-
Notifications
You must be signed in to change notification settings - Fork 903
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Defer window creation until UIApplicationMain
on iOS
#1121
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,13 @@ | ||
# Unreleased | ||
|
||
- On macOS, implement `run_return`. | ||
- On iOS 13.0, fix `NSInternalInconsistencyException` upon touching the screen. | ||
- On iOS, `Window::set_outer_position` is now ignored. | ||
- On iOS, `WindowBuilder::with_inner_size` is now ignored (to match behaviour | ||
`Window::set_inner_size`, and additionally when combined with `with_fullscreen` this used to | ||
result in an incorrect size for the window). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. *to match the behavior of |
||
- On iOS, `Window::inner_position` and `Window::inner_size` no longer include the safe area insets, | ||
and the safe area must now be queried with `WindowExtIOS::safe_area_screen_space`. | ||
|
||
# 0.20.0 Alpha 3 (2019-08-14) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,10 +14,11 @@ use crate::{ | |
use crate::platform_impl::platform::{ | ||
event_loop::{EventHandler, Never}, | ||
ffi::{ | ||
id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, | ||
kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, | ||
CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, | ||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSUInteger, | ||
CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, | ||
}, | ||
window::Inner as WindowInner, | ||
}; | ||
|
||
macro_rules! bug { | ||
|
@@ -30,11 +31,11 @@ macro_rules! bug { | |
#[derive(Debug)] | ||
enum AppStateImpl { | ||
NotLaunched { | ||
queued_windows: Vec<id>, | ||
queued_windows: Vec<*mut WindowInner>, | ||
queued_events: Vec<Event<Never>>, | ||
}, | ||
Launching { | ||
queued_windows: Vec<id>, | ||
queued_windows: Vec<*mut WindowInner>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be changed to |
||
queued_events: Vec<Event<Never>>, | ||
queued_event_handler: Box<dyn EventHandler>, | ||
}, | ||
|
@@ -56,26 +57,6 @@ enum AppStateImpl { | |
Terminated, | ||
} | ||
|
||
impl Drop for AppStateImpl { | ||
fn drop(&mut self) { | ||
match self { | ||
&mut AppStateImpl::NotLaunched { | ||
ref mut queued_windows, | ||
.. | ||
} | ||
| &mut AppStateImpl::Launching { | ||
ref mut queued_windows, | ||
.. | ||
} => unsafe { | ||
for &mut window in queued_windows { | ||
let () = msg_send![window, release]; | ||
} | ||
}, | ||
_ => {} | ||
} | ||
} | ||
} | ||
|
||
pub struct AppState { | ||
app_state: AppStateImpl, | ||
control_flow: ControlFlow, | ||
|
@@ -116,29 +97,36 @@ impl AppState { | |
RefMut::map(guard, |state| state.as_mut().unwrap()) | ||
} | ||
|
||
// requires main thread and window is a UIWindow | ||
// retains window | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A comment explaining why this function is/was |
||
pub unsafe fn set_key_window(window: id) { | ||
pub unsafe fn defer_window_init(window: *mut WindowInner) { | ||
let mut this = AppState::get_mut(); | ||
match &mut this.app_state { | ||
&mut AppStateImpl::NotLaunched { | ||
match this.app_state { | ||
// `UIApplicationMain` not called yet, so defer initialization | ||
AppStateImpl::NotLaunched { | ||
ref mut queued_windows, | ||
.. | ||
} => queued_windows.push(window), | ||
// `UIApplicationMain` already called, so initialize immediately | ||
_ => (*window).init(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we also should be queuing up windows during the |
||
} | ||
drop(this); | ||
} | ||
|
||
pub unsafe fn cancel_deferred_window_init(window: *mut WindowInner) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be unnecessary if appstate held onto |
||
let mut this = AppState::get_mut(); | ||
match this.app_state { | ||
AppStateImpl::NotLaunched { | ||
ref mut queued_windows, | ||
.. | ||
} | ||
| AppStateImpl::Launching { | ||
ref mut queued_windows, | ||
.. | ||
} => { | ||
queued_windows.push(window); | ||
msg_send![window, retain]; | ||
return; | ||
queued_windows.remove(queued_windows.iter().position(|x| x == &window).unwrap()); | ||
} | ||
&mut AppStateImpl::ProcessingEvents { .. } => {} | ||
&mut AppStateImpl::InUserCallback { .. } => {} | ||
&mut AppStateImpl::Terminated => panic!( | ||
"Attempt to create a `Window` \ | ||
after the app has terminated" | ||
), | ||
app_state => unreachable!("unexpected state: {:#?}", app_state), /* all other cases should be impossible */ | ||
_ => (), | ||
} | ||
drop(this); | ||
msg_send![window, makeKeyAndVisible] | ||
} | ||
|
||
// requires main thread | ||
|
@@ -171,11 +159,18 @@ impl AppState { | |
// requires main thread | ||
pub unsafe fn did_finish_launching() { | ||
let mut this = AppState::get_mut(); | ||
let windows = match &mut this.app_state { | ||
let (windows, events, event_handler) = match &mut this.app_state { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Getting the events/event_handler here is incorrect. We'll miss events that come in during window initialization. The previous code was correct. |
||
&mut AppStateImpl::Launching { | ||
ref mut queued_windows, | ||
ref mut queued_event_handler, | ||
ref mut queued_events, | ||
.. | ||
} => mem::replace(queued_windows, Vec::new()), | ||
} => { | ||
let windows = mem::replace(queued_windows, Vec::new()); | ||
let events = ptr::read(queued_events); | ||
let event_handler = ptr::read(queued_event_handler); | ||
(windows, events, event_handler) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This use of Note: I'm working on a PR to refactor AppState, and implement the request redraw API that will clean a lot of this up. |
||
} | ||
_ => panic!( | ||
"winit iOS expected the app to be in a `Launching` \ | ||
state, but was not - please file an issue" | ||
|
@@ -184,48 +179,17 @@ impl AppState { | |
// have to drop RefMut because the window setup code below can trigger new events | ||
drop(this); | ||
|
||
// Create UIKit views, view controllers and windows for each window | ||
for window in windows { | ||
let count: NSUInteger = msg_send![window, retainCount]; | ||
// make sure the window is still referenced | ||
if count > 1 { | ||
// Do a little screen dance here to account for windows being created before | ||
// `UIApplicationMain` is called. This fixes visual issues such as being | ||
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we | ||
// gotta reset the `rootViewController`. | ||
// | ||
// relevant iOS log: | ||
// ``` | ||
// [ApplicationLifecycle] Windows were created before application initialzation | ||
// completed. This may result in incorrect visual appearance. | ||
// ``` | ||
let screen: id = msg_send![window, screen]; | ||
let () = msg_send![screen, retain]; | ||
let () = msg_send![window, setScreen:0 as id]; | ||
let () = msg_send![window, setScreen: screen]; | ||
let () = msg_send![screen, release]; | ||
let controller: id = msg_send![window, rootViewController]; | ||
let () = msg_send![window, setRootViewController:ptr::null::<()>()]; | ||
let () = msg_send![window, setRootViewController: controller]; | ||
let () = msg_send![window, makeKeyAndVisible]; | ||
} | ||
let () = msg_send![window, release]; | ||
(*window).init(); | ||
} | ||
|
||
let mut this = AppState::get_mut(); | ||
let (windows, events, event_handler) = match &mut this.app_state { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was correct the way it was. |
||
&mut AppStateImpl::Launching { | ||
ref mut queued_windows, | ||
ref mut queued_events, | ||
ref mut queued_event_handler, | ||
} => { | ||
let windows = ptr::read(queued_windows); | ||
let events = ptr::read(queued_events); | ||
let event_handler = ptr::read(queued_event_handler); | ||
(windows, events, event_handler) | ||
} | ||
_ => panic!( | ||
"winit iOS expected the app to be in a `Launching` \ | ||
state, but was not - please file an issue" | ||
match this.app_state { | ||
AppStateImpl::Launching { .. } => (), | ||
_ => unreachable!( | ||
"winit iOS expected the app to be in a `Launching` state, but was not - please \ | ||
file an issue" | ||
), | ||
}; | ||
ptr::write( | ||
|
@@ -239,17 +203,6 @@ impl AppState { | |
|
||
let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); | ||
AppState::handle_nonuser_events(events); | ||
|
||
// the above window dance hack, could possibly trigger new windows to be created. | ||
// we can just set those windows up normally, as they were created after didFinishLaunching | ||
for window in windows { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is still necessary, although it would call Although, I see the interaction with L109. There are two things we want to guarantee.
Any idea how to guarantee both? They seem to be in conflict after this PR. |
||
let count: NSUInteger = msg_send![window, retainCount]; | ||
// make sure the window is still referenced | ||
if count > 1 { | ||
let () = msg_send![window, makeKeyAndVisible]; | ||
} | ||
let () = msg_send![window, release]; | ||
} | ||
} | ||
|
||
// requires main thread | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ use crate::{ | |
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, | ||
UIRectEdge, UITouchPhase, UITouchType, | ||
}, | ||
monitor, | ||
window::PlatformSpecificWindowBuilderAttributes, | ||
DeviceId, | ||
}, | ||
|
@@ -335,20 +336,31 @@ unsafe fn get_window_class() -> &'static Class { | |
CLASS.unwrap() | ||
} | ||
|
||
pub unsafe fn frame_from_window_attributes(window_attributes: &WindowAttributes) -> CGRect { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs a comment about why this is unsafe. |
||
let screen = match window_attributes.fullscreen { | ||
Some(Fullscreen::Exclusive(ref video_mode)) => { | ||
video_mode.video_mode.monitor.ui_screen() as id | ||
} | ||
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id, | ||
None => monitor::main_uiscreen().ui_screen(), | ||
}; | ||
msg_send![screen, bounds] | ||
} | ||
|
||
// requires main thread | ||
pub unsafe fn create_view( | ||
_window_attributes: &WindowAttributes, | ||
window_attributes: &WindowAttributes, | ||
platform_attributes: &PlatformSpecificWindowBuilderAttributes, | ||
frame: CGRect, | ||
) -> id { | ||
let class = get_view_class(platform_attributes.root_view_class); | ||
|
||
let view: id = msg_send![class, alloc]; | ||
assert!(!view.is_null(), "Failed to create `UIView` instance"); | ||
let view: id = msg_send![view, initWithFrame: frame]; | ||
let view: id = msg_send![ | ||
view, | ||
initWithFrame: frame_from_window_attributes(&window_attributes) | ||
]; | ||
assert!(!view.is_null(), "Failed to initialize `UIView` instance"); | ||
let () = msg_send![view, setMultipleTouchEnabled: YES]; | ||
|
||
view | ||
} | ||
|
||
|
@@ -360,12 +372,12 @@ pub unsafe fn create_view_controller( | |
) -> id { | ||
let class = get_view_controller_class(); | ||
|
||
let view_controller: id = msg_send![class, alloc]; | ||
let mut view_controller: id = msg_send![class, alloc]; | ||
assert!( | ||
!view_controller.is_null(), | ||
"Failed to create `UIViewController` instance" | ||
); | ||
let view_controller: id = msg_send![view_controller, init]; | ||
view_controller = msg_send![view_controller, init]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the random coding style change? I'm partial to the immutable version. |
||
assert!( | ||
!view_controller.is_null(), | ||
"Failed to initialize `UIViewController` instance" | ||
|
@@ -412,14 +424,15 @@ pub unsafe fn create_view_controller( | |
pub unsafe fn create_window( | ||
window_attributes: &WindowAttributes, | ||
platform_attributes: &PlatformSpecificWindowBuilderAttributes, | ||
frame: CGRect, | ||
view_controller: id, | ||
) -> id { | ||
let class = get_window_class(); | ||
|
||
let window: id = msg_send![class, alloc]; | ||
let mut window: id = msg_send![class, alloc]; | ||
assert!(!window.is_null(), "Failed to create `UIWindow` instance"); | ||
let window: id = msg_send![window, initWithFrame: frame]; | ||
window = msg_send![ | ||
window, | ||
initWithFrame: frame_from_window_attributes(&window_attributes) | ||
]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same code style nit here. |
||
assert!( | ||
!window.is_null(), | ||
"Failed to initialize `UIWindow` instance" | ||
|
@@ -439,6 +452,7 @@ pub unsafe fn create_window( | |
} | ||
None => (), | ||
} | ||
let () = msg_send![window, makeKeyAndVisible]; | ||
|
||
window | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this bug simulator only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can also reproduce this on my iPhone 6s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah. Bummer