diff --git a/Cargo.toml b/Cargo.toml index 348d3423b..f71e03d29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,12 +117,12 @@ android-activity = "0.6.0" ndk = { version = "0.9.0", default-features = false } [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] +block2 = "0.5.1" core-foundation = "0.9.3" objc2 = "0.5.2" [target.'cfg(target_os = "macos")'.dependencies] core-graphics = "0.23.1" -block2 = "0.5.1" [target.'cfg(target_os = "macos")'.dependencies.objc2-foundation] version = "0.2.2" @@ -180,11 +180,13 @@ features = [ [target.'cfg(target_os = "ios")'.dependencies.objc2-foundation] version = "0.2.2" features = [ + "block2", "dispatch", "NSArray", "NSEnumerator", "NSGeometry", "NSObjCRuntime", + "NSOperation", "NSString", "NSProcessInfo", "NSThread", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index cb53b417f..812e5f0f7 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -49,6 +49,10 @@ changelog entry. ### Changed - On macOS, no longer need control of the main `NSApplication` class (which means you can now override it yourself). +- On iOS, remove custom application delegates. You are now allowed to override the + application delegate yourself. +- On iOS, no longer act as-if the application successfully open all URLs. Override + `application:didFinishLaunchingWithOptions:` and provide the desired behaviour yourself. ### Fixed diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 59f539616..6e6287683 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -3,11 +3,14 @@ //! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on //! iOS 9.3. //! +//! ## Window initialization +//! //! iOS's main `UIApplicationMain` does some init work that's required by all //! UI-related code (see issue [#1705]). It is best to create your windows -//! inside `Event::Resumed`. +//! inside [`ApplicationHandler::resumed`]. //! //! [#1705]: https://github.com/rust-windowing/winit/issues/1705 +//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed //! //! ## Building app //! @@ -63,6 +66,16 @@ //! opengl will result in segfault. //! //! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed. +//! +//! ## Custom `UIApplicationDelegate` +//! +//! Winit usually handles everything related to the lifecycle events of the application. Sometimes, +//! though, you might want to access some of the more niche stuff that [the application +//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it +//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not +//! register an application delegate, so you can set up a custom one in a nib file instead. +//! +//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc use std::os::raw::c_void; diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 7d4b8ca34..30623b01e 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -3,16 +3,84 @@ //! Winit has an OS requirement of macOS 10.11 or higher (same as Rust //! itself), and is regularly tested on macOS 10.14. //! +//! ## Window initialization +//! //! A lot of functionality expects the application to be ready before you //! start doing anything; this includes creating windows, fetching monitors, //! drawing, and so on, see issues [#2238], [#2051] and [#2087]. //! //! If you encounter problems, you should try doing your initialization inside -//! `Event::Resumed`. +//! [`ApplicationHandler::resumed`]. //! //! [#2238]: https://github.com/rust-windowing/winit/issues/2238 //! [#2051]: https://github.com/rust-windowing/winit/issues/2051 //! [#2087]: https://github.com/rust-windowing/winit/issues/2087 +//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed +//! +//! ## Custom `NSApplicationDelegate` +//! +//! Winit usually handles everything related to the lifecycle events of the application. Sometimes, +//! though, you might want to do more niche stuff, such as [handle when the user re-activates the +//! application][reopen]. Such functionality is not exposed directly in Winit, since it would +//! increase the API surface by quite a lot. +//! +//! [reopen]: https://developer.apple.com/documentation/appkit/nsapplicationdelegate/1428638-applicationshouldhandlereopen?language=objc +//! +//! Instead, Winit guarantees that it will not register an application delegate, so the solution is +//! to register your own application delegate, as outlined in the following example (see +//! `objc2-app-kit` for more detailed information). +#![cfg_attr(target_os = "macos", doc = "```")] +#![cfg_attr(not(target_os = "macos"), doc = "```ignore")] +//! use objc2::rc::Retained; +//! use objc2::runtime::ProtocolObject; +//! use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass}; +//! use objc2_app_kit::{NSApplication, NSApplicationDelegate}; +//! use objc2_foundation::{NSArray, NSURL, MainThreadMarker, NSObject, NSObjectProtocol}; +//! use winit::event_loop::EventLoop; +//! +//! declare_class!( +//! struct AppDelegate; +//! +//! unsafe impl ClassType for AppDelegate { +//! type Super = NSObject; +//! type Mutability = mutability::MainThreadOnly; +//! const NAME: &'static str = "MyAppDelegate"; +//! } +//! +//! impl DeclaredClass for AppDelegate {} +//! +//! unsafe impl NSObjectProtocol for AppDelegate {} +//! +//! unsafe impl NSApplicationDelegate for AppDelegate { +//! #[method(application:openURLs:)] +//! fn application_openURLs(&self, application: &NSApplication, urls: &NSArray) { +//! // Note: To specifically get `application:openURLs:` to work, you _might_ +//! // have to bundle your application. This is not done in this example. +//! println!("open urls: {application:?}, {urls:?}"); +//! } +//! } +//! ); +//! +//! impl AppDelegate { +//! fn new(mtm: MainThreadMarker) -> Retained { +//! unsafe { msg_send_id![super(mtm.alloc().set_ivars(())), init] } +//! } +//! } +//! +//! fn main() -> Result<(), Box> { +//! let event_loop = EventLoop::new()?; +//! +//! let mtm = MainThreadMarker::new().unwrap(); +//! let delegate = AppDelegate::new(mtm); +//! // Important: Call `sharedApplication` after `EventLoop::new`, +//! // doing it before is not yet supported. +//! let app = NSApplication::sharedApplication(mtm); +//! app.setDelegate(Some(ProtocolObject::from_ref(&*delegate))); +//! +//! // event_loop.run_app(&mut my_app); +//! Ok(()) +//! } +//! ``` use std::os::raw::c_void; diff --git a/src/platform_impl/ios/app_delegate.rs b/src/platform_impl/ios/app_delegate.rs deleted file mode 100644 index 2328c58c5..000000000 --- a/src/platform_impl/ios/app_delegate.rs +++ /dev/null @@ -1,60 +0,0 @@ -use objc2::{declare_class, mutability, ClassType, DeclaredClass}; -use objc2_foundation::{MainThreadMarker, NSObject}; -use objc2_ui_kit::UIApplication; - -use super::app_state::{self, send_occluded_event_for_all_windows, EventWrapper}; -use crate::event::Event; - -declare_class!( - pub struct AppDelegate; - - unsafe impl ClassType for AppDelegate { - type Super = NSObject; - type Mutability = mutability::InteriorMutable; - const NAME: &'static str = "WinitApplicationDelegate"; - } - - impl DeclaredClass for AppDelegate {} - - // UIApplicationDelegate protocol - unsafe impl AppDelegate { - #[method(application:didFinishLaunchingWithOptions:)] - fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { - app_state::did_finish_launching(MainThreadMarker::new().unwrap()); - true - } - - #[method(applicationDidBecomeActive:)] - fn did_become_active(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)) - } - - #[method(applicationWillResignActive:)] - fn will_resign_active(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)) - } - - #[method(applicationWillEnterForeground:)] - fn will_enter_foreground(&self, application: &UIApplication) { - send_occluded_event_for_all_windows(application, false); - } - - #[method(applicationDidEnterBackground:)] - fn did_enter_background(&self, application: &UIApplication) { - send_occluded_event_for_all_windows(application, true); - } - - #[method(applicationWillTerminate:)] - fn will_terminate(&self, application: &UIApplication) { - app_state::terminated(application); - } - - #[method(applicationDidReceiveMemoryWarning:)] - fn did_receive_memory_warning(&self, _application: &UIApplication) { - let mtm = MainThreadMarker::new().unwrap(); - app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::MemoryWarning)) - } - } -); diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index 52d8062be..a093b7b59 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -13,8 +13,14 @@ use core_foundation::runloop::{ }; use objc2::rc::Retained; use objc2::{msg_send_id, ClassType}; -use objc2_foundation::{MainThreadMarker, NSString}; -use objc2_ui_kit::{UIApplication, UIApplicationMain, UIDevice, UIScreen, UIUserInterfaceIdiom}; +use objc2_foundation::{MainThreadMarker, NSNotificationCenter, NSObject}; +use objc2_ui_kit::{ + UIApplication, UIApplicationDidBecomeActiveNotification, + UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification, + UIApplicationDidReceiveMemoryWarningNotification, UIApplicationMain, + UIApplicationWillEnterForegroundNotification, UIApplicationWillResignActiveNotification, + UIApplicationWillTerminateNotification, UIDevice, UIScreen, UIUserInterfaceIdiom, +}; use crate::error::EventLoopError; use crate::event::Event; @@ -25,8 +31,8 @@ use crate::platform::ios::Idiom; use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents}; use crate::window::{CustomCursor, CustomCursorSource, Theme}; -use super::app_delegate::AppDelegate; -use super::app_state::AppState; +use super::app_state::{send_occluded_event_for_all_windows, AppState, EventWrapper}; +use super::notification_center::create_observer; use super::{app_state, monitor, MonitorHandle}; #[derive(Debug)] @@ -132,6 +138,18 @@ pub struct EventLoop { sender: Sender, receiver: Receiver, window_target: RootActiveEventLoop, + + // Since iOS 9.0, we no longer need to remove the observers before they are deallocated; the + // system instead cleans it up next time it would have posted a notification to it. + // + // Though we do still need to keep the observers around to prevent them from being deallocated. + _did_finish_launching_observer: Retained, + _did_become_active_observer: Retained, + _will_resign_active_observer: Retained, + _will_enter_foreground_observer: Retained, + _did_enter_background_observer: Retained, + _will_terminate_observer: Retained, + _did_receive_memory_warning_observer: Retained, } #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -158,11 +176,97 @@ impl EventLoop { // this line sets up the main run loop before `UIApplicationMain` setup_control_flow_observers(); + let center = unsafe { NSNotificationCenter::defaultCenter() }; + + let _did_finish_launching_observer = create_observer( + ¢er, + // `application:didFinishLaunchingWithOptions:` + unsafe { UIApplicationDidFinishLaunchingNotification }, + move |_| { + app_state::did_finish_launching(mtm); + }, + ); + let _did_become_active_observer = create_observer( + ¢er, + // `applicationDidBecomeActive:` + unsafe { UIApplicationDidBecomeActiveNotification }, + move |_| { + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed)); + }, + ); + let _will_resign_active_observer = create_observer( + ¢er, + // `applicationWillResignActive:` + unsafe { UIApplicationWillResignActiveNotification }, + move |_| { + app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended)); + }, + ); + let _will_enter_foreground_observer = create_observer( + ¢er, + // `applicationWillEnterForeground:` + unsafe { UIApplicationWillEnterForegroundNotification }, + move |notification| { + let app = unsafe { notification.object() }.expect( + "UIApplicationWillEnterForegroundNotification to have application object", + ); + // SAFETY: The `object` in `UIApplicationWillEnterForegroundNotification` is + // documented to be `UIApplication`. + let app: Retained = unsafe { Retained::cast(app) }; + send_occluded_event_for_all_windows(&app, false); + }, + ); + let _did_enter_background_observer = create_observer( + ¢er, + // `applicationDidEnterBackground:` + unsafe { UIApplicationDidEnterBackgroundNotification }, + move |notification| { + let app = unsafe { notification.object() }.expect( + "UIApplicationDidEnterBackgroundNotification to have application object", + ); + // SAFETY: The `object` in `UIApplicationDidEnterBackgroundNotification` is + // documented to be `UIApplication`. + let app: Retained = unsafe { Retained::cast(app) }; + send_occluded_event_for_all_windows(&app, true); + }, + ); + let _will_terminate_observer = create_observer( + ¢er, + // `applicationWillTerminate:` + unsafe { UIApplicationWillTerminateNotification }, + move |notification| { + let app = unsafe { notification.object() } + .expect("UIApplicationWillTerminateNotification to have application object"); + // SAFETY: The `object` in `UIApplicationWillTerminateNotification` is + // (somewhat) documented to be `UIApplication`. + let app: Retained = unsafe { Retained::cast(app) }; + app_state::terminated(&app); + }, + ); + let _did_receive_memory_warning_observer = create_observer( + ¢er, + // `applicationDidReceiveMemoryWarning:` + unsafe { UIApplicationDidReceiveMemoryWarningNotification }, + move |_| { + app_state::handle_nonuser_event( + mtm, + EventWrapper::StaticEvent(Event::MemoryWarning), + ); + }, + ); + Ok(EventLoop { mtm, sender, receiver, window_target: RootActiveEventLoop { p: ActiveEventLoop { mtm }, _marker: PhantomData }, + _did_finish_launching_observer, + _did_become_active_observer, + _will_resign_active_observer, + _will_enter_foreground_observer, + _did_enter_background_observer, + _will_terminate_observer, + _did_receive_memory_warning_observer, }) } @@ -192,9 +296,6 @@ impl EventLoop { app_state::will_launch(self.mtm, handler); - // Ensure application delegate is initialized - let _ = AppDelegate::class(); - extern "C" { // These functions are in crt_externs.h. fn _NSGetArgc() -> *mut c_int; @@ -205,8 +306,10 @@ impl EventLoop { UIApplicationMain( *_NSGetArgc(), NonNull::new(*_NSGetArgv()).unwrap(), + // We intentionally override neither the application nor the delegate, to allow the + // user to do so themselves! + None, None, - Some(&NSString::from_str(AppDelegate::NAME)), ) }; unreachable!() diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 9c2362e11..69e79c921 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -1,9 +1,9 @@ #![allow(clippy::let_unit_value)] -mod app_delegate; mod app_state; mod event_loop; mod monitor; +mod notification_center; mod view; mod view_controller; mod window; diff --git a/src/platform_impl/ios/notification_center.rs b/src/platform_impl/ios/notification_center.rs new file mode 100644 index 000000000..652bf1d07 --- /dev/null +++ b/src/platform_impl/ios/notification_center.rs @@ -0,0 +1,27 @@ +use std::ptr::NonNull; + +use block2::RcBlock; +use objc2::rc::Retained; +use objc2_foundation::{NSNotification, NSNotificationCenter, NSNotificationName, NSObject}; + +/// Observe the given notification. +/// +/// This is used in Winit as an alternative to declaring an application delegate, as we want to +/// give the user full control over those. +pub fn create_observer( + center: &NSNotificationCenter, + name: &NSNotificationName, + handler: impl Fn(&NSNotification) + 'static, +) -> Retained { + let block = RcBlock::new(move |notification: NonNull| { + handler(unsafe { notification.as_ref() }); + }); + unsafe { + center.addObserverForName_object_queue_usingBlock( + Some(name), + None, // No sender filter + None, // No queue, run on posting thread (i.e. main thread) + &block, + ) + } +}