Skip to content

Commit 57baf72

Browse files
madsmtmkchibisov
authored andcommitted
Allow the user to register the application delegate on iOS
iOS parts of #3758. This allows the user to override the application delegate themselves, which opens several doors for customization that were previously closed. To do this, we use notifications instead of a top-level application delegate. One effect of not providing an application delegate on iOS is that we no longer act as-if the application successfully open all URLs there. This is a breaking change, although unlikely to matter in practice, since the return value of `application:didFinishLaunchingWithOptions:` is seldom used by the system (and this is likely the preferred behaviour anyhow).
1 parent da7a096 commit 57baf72

8 files changed

Lines changed: 229 additions & 72 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,12 @@ android-activity = "0.6.0"
117117
ndk = { version = "0.9.0", default-features = false }
118118

119119
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
120+
block2 = "0.5.1"
120121
core-foundation = "0.9.3"
121122
objc2 = "0.5.2"
122123

123124
[target.'cfg(target_os = "macos")'.dependencies]
124125
core-graphics = "0.23.1"
125-
block2 = "0.5.1"
126126

127127
[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
128128
version = "0.2.2"
@@ -180,11 +180,13 @@ features = [
180180
[target.'cfg(target_os = "ios")'.dependencies.objc2-foundation]
181181
version = "0.2.2"
182182
features = [
183+
"block2",
183184
"dispatch",
184185
"NSArray",
185186
"NSEnumerator",
186187
"NSGeometry",
187188
"NSObjCRuntime",
189+
"NSOperation",
188190
"NSString",
189191
"NSProcessInfo",
190192
"NSThread",

src/changelog/unreleased.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ changelog entry.
4949
### Changed
5050

5151
- On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself).
52+
- On iOS, remove custom application delegates. You are now allowed to override the
53+
application delegate yourself.
54+
- On iOS, no longer act as-if the application successfully open all URLs. Override
55+
`application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself.
5256

5357
### Fixed
5458

src/platform/ios.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
44
//! iOS 9.3.
55
//!
6+
//! ## Window initialization
7+
//!
68
//! iOS's main `UIApplicationMain` does some init work that's required by all
79
//! UI-related code (see issue [#1705]). It is best to create your windows
8-
//! inside `Event::Resumed`.
10+
//! inside [`ApplicationHandler::resumed`].
911
//!
1012
//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
13+
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
1114
//!
1215
//! ## Building app
1316
//!
@@ -63,6 +66,16 @@
6366
//! opengl will result in segfault.
6467
//!
6568
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
69+
//!
70+
//! ## Custom `UIApplicationDelegate`
71+
//!
72+
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
73+
//! though, you might want to access some of the more niche stuff that [the application
74+
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
75+
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
76+
//! register an application delegate, so you can set up a custom one in a nib file instead.
77+
//!
78+
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
6679
6780
use std::os::raw::c_void;
6881

src/platform/macos.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,84 @@
33
//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
44
//! itself), and is regularly tested on macOS 10.14.
55
//!
6+
//! ## Window initialization
7+
//!
68
//! A lot of functionality expects the application to be ready before you
79
//! start doing anything; this includes creating windows, fetching monitors,
810
//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
911
//!
1012
//! If you encounter problems, you should try doing your initialization inside
11-
//! `Event::Resumed`.
13+
//! [`ApplicationHandler::resumed`].
1214
//!
1315
//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
1416
//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
1517
//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
18+
//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
19+
//!
20+
//! ## Custom `NSApplicationDelegate`
21+
//!
22+
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
23+
//! though, you might want to do more niche stuff, such as [handle when the user re-activates the
24+
//! application][reopen]. Such functionality is not exposed directly in Winit, since it would
25+
//! increase the API surface by quite a lot.
26+
//!
27+
//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc
28+
//!
29+
//! Instead, Winit guarantees that it will not register an application delegate, so the solution is
30+
//! to register your own application delegate, as outlined in the following example (see
31+
//! `objc2-app-kit` for more detailed information).
32+
#![cfg_attr(target_os = "macos", doc = "```")]
33+
#![cfg_attr(not(target_os = "macos"), doc = "```ignore")]
34+
//! use objc2::rc::Retained;
35+
//! use objc2::runtime::ProtocolObject;
36+
//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
37+
//! use objc2_app_kit::{NSApplication, NSApplicationDelegate};
38+
//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol};
39+
//! use winit::event_loop::EventLoop;
40+
//!
41+
//! declare_class!(
42+
//! struct AppDelegate;
43+
//!
44+
//! unsafe impl ClassType for AppDelegate {
45+
//! type Super = NSObject;
46+
//! type Mutability = mutability::MainThreadOnly;
47+
//! const NAME: &'static str = "MyAppDelegate";
48+
//! }
49+
//!
50+
//! impl DeclaredClass for AppDelegate {}
51+
//!
52+
//! unsafe impl NSObjectProtocol for AppDelegate {}
53+
//!
54+
//! unsafe impl NSApplicationDelegate for AppDelegate {
55+
//! #[method(application:openURLs:)]
56+
//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray<NSURL>) {
57+
//! // Note: To specifically get `application:openURLs:` to work, you _might_
58+
//! // have to bundle your application. This is not done in this example.
59+
//! println!("open urls: {application:?}, {urls:?}");
60+
//! }
61+
//! }
62+
//! );
63+
//!
64+
//! impl AppDelegate {
65+
//! fn new(mtm: MainThreadMarker) -> Retained<Self> {
66+
//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] }
67+
//! }
68+
//! }
69+
//!
70+
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
71+
//! let event_loop = EventLoop::new()?;
72+
//!
73+
//! let mtm = MainThreadMarker::new().unwrap();
74+
//! let delegate = AppDelegate::new(mtm);
75+
//! // Important: Call `sharedApplication` after `EventLoop::new`,
76+
//! // doing it before is not yet supported.
77+
//! let app = NSApplication::sharedApplication(mtm);
78+
//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));
79+
//!
80+
//! // event_loop.run_app(&mut my_app);
81+
//! Ok(())
82+
//! }
83+
//! ```
1684
1785
use std::os::raw::c_void;
1886

src/platform_impl/ios/app_delegate.rs

Lines changed: 0 additions & 60 deletions
This file was deleted.

src/platform_impl/ios/event_loop.rs

Lines changed: 111 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,14 @@ use core_foundation::runloop::{
1313
};
1414
use objc2::rc::Retained;
1515
use objc2::{msg_send_id, ClassType};
16-
use objc2_foundation::{MainThreadMarker, NSString};
17-
use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom};
16+
use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject};
17+
use objc2_ui_kit::{
18+
UIApplication, UIApplicationDidBecomeActiveNotification,
19+
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
20+
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain,
21+
UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification,
22+
UIApplicationWillTerminateNotification, UIDevice, UIScreen, UIUserInterfaceIdiom,
23+
};
1824

1925
use crate::error::EventLoopError;
2026
use crate::event::Event;
@@ -25,8 +31,8 @@ use crate::platform::ios::Idiom;
2531
use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents};
2632
use crate::window::{CustomCursor, CustomCursorSource, Theme};
2733

28-
use super::app_delegate::AppDelegate;
29-
use super::app_state::AppState;
34+
use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper};
35+
use super::notification_center::create_observer;
3036
use super::{app_state, monitor, MonitorHandle};
3137

3238
#[derive(Debug)]
@@ -132,6 +138,18 @@ pub struct EventLoop<T: 'static> {
132138
sender: Sender<T>,
133139
receiver: Receiver<T>,
134140
window_target: RootActiveEventLoop,
141+
142+
// Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the
143+
// system instead cleans it up next time it would have posted a notification to it.
144+
//
145+
// Though we do still need to keep the observers around to prevent them from being deallocated.
146+
_did_finish_launching_observer: Retained<NSObject>,
147+
_did_become_active_observer: Retained<NSObject>,
148+
_will_resign_active_observer: Retained<NSObject>,
149+
_will_enter_foreground_observer: Retained<NSObject>,
150+
_did_enter_background_observer: Retained<NSObject>,
151+
_will_terminate_observer: Retained<NSObject>,
152+
_did_receive_memory_warning_observer: Retained<NSObject>,
135153
}
136154

137155
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
@@ -158,11 +176,97 @@ impl<T: 'static> EventLoop<T> {
158176
// this line sets up the main run loop before `UIApplicationMain`
159177
setup_control_flow_observers();
160178

179+
let center = unsafe { NSNotificationCenter::defaultCenter() };
180+
181+
let _did_finish_launching_observer = create_observer(
182+
&center,
183+
// `application:didFinishLaunchingWithOptions:`
184+
unsafe { UIApplicationDidFinishLaunchingNotification },
185+
move |_| {
186+
app_state::did_finish_launching(mtm);
187+
},
188+
);
189+
let _did_become_active_observer = create_observer(
190+
&center,
191+
// `applicationDidBecomeActive:`
192+
unsafe { UIApplicationDidBecomeActiveNotification },
193+
move |_| {
194+
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed));
195+
},
196+
);
197+
let _will_resign_active_observer = create_observer(
198+
&center,
199+
// `applicationWillResignActive:`
200+
unsafe { UIApplicationWillResignActiveNotification },
201+
move |_| {
202+
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended));
203+
},
204+
);
205+
let _will_enter_foreground_observer = create_observer(
206+
&center,
207+
// `applicationWillEnterForeground:`
208+
unsafe { UIApplicationWillEnterForegroundNotification },
209+
move |notification| {
210+
let app = unsafe { notification.object() }.expect(
211+
"UIApplicationWillEnterForegroundNotification to have application object",
212+
);
213+
// SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is
214+
// documented to be `UIApplication`.
215+
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
216+
send_occluded_event_for_all_windows(&app, false);
217+
},
218+
);
219+
let _did_enter_background_observer = create_observer(
220+
&center,
221+
// `applicationDidEnterBackground:`
222+
unsafe { UIApplicationDidEnterBackgroundNotification },
223+
move |notification| {
224+
let app = unsafe { notification.object() }.expect(
225+
"UIApplicationDidEnterBackgroundNotification to have application object",
226+
);
227+
// SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is
228+
// documented to be `UIApplication`.
229+
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
230+
send_occluded_event_for_all_windows(&app, true);
231+
},
232+
);
233+
let _will_terminate_observer = create_observer(
234+
&center,
235+
// `applicationWillTerminate:`
236+
unsafe { UIApplicationWillTerminateNotification },
237+
move |notification| {
238+
let app = unsafe { notification.object() }
239+
.expect("UIApplicationWillTerminateNotification to have application object");
240+
// SAFETY: The `object` in `UIApplicationWillTerminateNotification` is
241+
// (somewhat) documented to be `UIApplication`.
242+
let app: Retained<UIApplication> = unsafe { Retained::cast(app) };
243+
app_state::terminated(&app);
244+
},
245+
);
246+
let _did_receive_memory_warning_observer = create_observer(
247+
&center,
248+
// `applicationDidReceiveMemoryWarning:`
249+
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
250+
move |_| {
251+
app_state::handle_nonuser_event(
252+
mtm,
253+
EventWrapper::StaticEvent(Event::MemoryWarning),
254+
);
255+
},
256+
);
257+
161258
Ok(EventLoop {
162259
mtm,
163260
sender,
164261
receiver,
165262
window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData },
263+
_did_finish_launching_observer,
264+
_did_become_active_observer,
265+
_will_resign_active_observer,
266+
_will_enter_foreground_observer,
267+
_did_enter_background_observer,
268+
_will_terminate_observer,
269+
_did_receive_memory_warning_observer,
166270
})
167271
}
168272

@@ -192,9 +296,6 @@ impl<T: 'static> EventLoop<T> {
192296

193297
app_state::will_launch(self.mtm, handler);
194298

195-
// Ensure application delegate is initialized
196-
let _ = AppDelegate::class();
197-
198299
extern "C" {
199300
// These functions are in crt_externs.h.
200301
fn _NSGetArgc() -> *mut c_int;
@@ -205,8 +306,10 @@ impl<T: 'static> EventLoop<T> {
205306
UIApplicationMain(
206307
*_NSGetArgc(),
207308
NonNull::new(*_NSGetArgv()).unwrap(),
309+
// We intentionally override neither the application nor the delegate, to allow the
310+
// user to do so themselves!
311+
None,
208312
None,
209-
Some(&NSString::from_str(AppDelegate::NAME)),
210313
)
211314
};
212315
unreachable!()

src/platform_impl/ios/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#![allow(clippy::let_unit_value)]
22

3-
mod app_delegate;
43
mod app_state;
54
mod event_loop;
65
mod monitor;
6+
mod notification_center;
77
mod view;
88
mod view_controller;
99
mod window;

0 commit comments

Comments
 (0)