winit/platform/
ios.rs

1//! # iOS / UIKit
2//!
3//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
4//! iOS 9.3.
5//!
6//! ## Window initialization
7//!
8//! iOS's main `UIApplicationMain` does some init work that's required by all
9//! UI-related code (see issue [#1705]). It is best to create your windows
10//! inside [`ApplicationHandler::resumed`].
11//!
12//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
13//! [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
14//!
15//! ## Building app
16//!
17//! To build ios app you will need rustc built for this targets:
18//!
19//!  - armv7-apple-ios
20//!  - armv7s-apple-ios
21//!  - i386-apple-ios
22//!  - aarch64-apple-ios
23//!  - x86_64-apple-ios
24//!
25//! Then
26//!
27//! ```
28//! cargo build --target=...
29//! ```
30//! The simplest way to integrate your app into xcode environment is to build it
31//! as a static library. Wrap your main function and export it.
32//!
33//! ```rust, ignore
34//! #[no_mangle]
35//! pub extern fn start_winit_app() {
36//!     start_inner()
37//! }
38//!
39//! fn start_inner() {
40//!    ...
41//! }
42//! ```
43//!
44//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
45//!
46//! ```ignore
47//! void start_winit_app();
48//! ```
49//!
50//! Use start_winit_app inside your xcode's main function.
51//!
52//!
53//! ## App lifecycle and events
54//!
55//! iOS environment is very different from other platforms and you must be very
56//! careful with it's events. Familiarize yourself with
57//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
58//!
59//! This is how those event are represented in winit:
60//!
61//!  - applicationDidBecomeActive is Resumed
62//!  - applicationWillResignActive is Suspended
63//!  - applicationWillTerminate is LoopExiting
64//!
65//! Keep in mind that after LoopExiting event is received every attempt to draw with
66//! opengl will result in segfault.
67//!
68//! 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
79
80use std::os::raw::c_void;
81
82use crate::event_loop::EventLoop;
83use crate::monitor::{MonitorHandle, VideoModeHandle};
84use crate::window::{Window, WindowAttributes};
85
86/// Additional methods on [`EventLoop`] that are specific to iOS.
87pub trait EventLoopExtIOS {
88    /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
89    fn idiom(&self) -> Idiom;
90}
91
92impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
93    fn idiom(&self) -> Idiom {
94        self.event_loop.idiom()
95    }
96}
97
98/// Additional methods on [`Window`] that are specific to iOS.
99pub trait WindowExtIOS {
100    /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
101    ///
102    /// The default value is device dependent, and it's recommended GLES or Metal applications set
103    /// this to [`MonitorHandle::scale_factor()`].
104    ///
105    /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
106    /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
107    fn set_scale_factor(&self, scale_factor: f64);
108
109    /// Sets the valid orientations for the [`Window`].
110    ///
111    /// The default value is [`ValidOrientations::LandscapeAndPortrait`].
112    ///
113    /// This changes the value returned by
114    /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
115    /// and then calls
116    /// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
117    fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
118
119    /// Sets whether the [`Window`] prefers the home indicator hidden.
120    ///
121    /// The default is to prefer showing the home indicator.
122    ///
123    /// This changes the value returned by
124    /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
125    /// and then calls
126    /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
127    ///
128    /// This only has an effect on iOS 11.0+.
129    fn set_prefers_home_indicator_hidden(&self, hidden: bool);
130
131    /// Sets the screen edges for which the system gestures will take a lower priority than the
132    /// application's touch handling.
133    ///
134    /// This changes the value returned by
135    /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
136    /// and then calls
137    /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
138    ///
139    /// This only has an effect on iOS 11.0+.
140    fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
141
142    /// Sets whether the [`Window`] prefers the status bar hidden.
143    ///
144    /// The default is to prefer showing the status bar.
145    ///
146    /// This sets the value of the
147    /// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
148    /// property.
149    ///
150    /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
151    /// is also called for you.
152    fn set_prefers_status_bar_hidden(&self, hidden: bool);
153
154    /// Sets the preferred status bar style for the [`Window`].
155    ///
156    /// The default is system-defined.
157    ///
158    /// This sets the value of the
159    /// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
160    /// property.
161    ///
162    /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
163    /// is also called for you.
164    fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
165
166    /// Sets whether the [`Window`] should recognize pinch gestures.
167    ///
168    /// The default is to not recognize gestures.
169    fn recognize_pinch_gesture(&self, should_recognize: bool);
170
171    /// Sets whether the [`Window`] should recognize pan gestures.
172    ///
173    /// The default is to not recognize gestures.
174    /// Installs [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) onto view
175    ///
176    /// Set the minimum number of touches required: [`minimumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-minimumnumberoftouches)
177    ///
178    /// Set the maximum number of touches recognized: [`maximumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-maximumnumberoftouches)
179    fn recognize_pan_gesture(
180        &self,
181        should_recognize: bool,
182        minimum_number_of_touches: u8,
183        maximum_number_of_touches: u8,
184    );
185
186    /// Sets whether the [`Window`] should recognize double tap gestures.
187    ///
188    /// The default is to not recognize gestures.
189    fn recognize_doubletap_gesture(&self, should_recognize: bool);
190
191    /// Sets whether the [`Window`] should recognize rotation gestures.
192    ///
193    /// The default is to not recognize gestures.
194    fn recognize_rotation_gesture(&self, should_recognize: bool);
195}
196
197impl WindowExtIOS for Window {
198    #[inline]
199    fn set_scale_factor(&self, scale_factor: f64) {
200        self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
201    }
202
203    #[inline]
204    fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
205        self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
206    }
207
208    #[inline]
209    fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
210        self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
211    }
212
213    #[inline]
214    fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
215        self.window.maybe_queue_on_main(move |w| {
216            w.set_preferred_screen_edges_deferring_system_gestures(edges)
217        })
218    }
219
220    #[inline]
221    fn set_prefers_status_bar_hidden(&self, hidden: bool) {
222        self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
223    }
224
225    #[inline]
226    fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
227        self.window.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
228    }
229
230    #[inline]
231    fn recognize_pinch_gesture(&self, should_recognize: bool) {
232        self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
233    }
234
235    #[inline]
236    fn recognize_pan_gesture(
237        &self,
238        should_recognize: bool,
239        minimum_number_of_touches: u8,
240        maximum_number_of_touches: u8,
241    ) {
242        self.window.maybe_queue_on_main(move |w| {
243            w.recognize_pan_gesture(
244                should_recognize,
245                minimum_number_of_touches,
246                maximum_number_of_touches,
247            )
248        });
249    }
250
251    #[inline]
252    fn recognize_doubletap_gesture(&self, should_recognize: bool) {
253        self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
254    }
255
256    #[inline]
257    fn recognize_rotation_gesture(&self, should_recognize: bool) {
258        self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
259    }
260}
261
262/// Additional methods on [`WindowAttributes`] that are specific to iOS.
263pub trait WindowAttributesExtIOS {
264    /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
265    ///
266    /// The default value is device dependent, and it's recommended GLES or Metal applications set
267    /// this to [`MonitorHandle::scale_factor()`].
268    ///
269    /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
270    /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
271    fn with_scale_factor(self, scale_factor: f64) -> Self;
272
273    /// Sets the valid orientations for the [`Window`].
274    ///
275    /// The default value is [`ValidOrientations::LandscapeAndPortrait`].
276    ///
277    /// This sets the initial value returned by
278    /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
279    fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
280
281    /// Sets whether the [`Window`] prefers the home indicator hidden.
282    ///
283    /// The default is to prefer showing the home indicator.
284    ///
285    /// This sets the initial value returned by
286    /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
287    ///
288    /// This only has an effect on iOS 11.0+.
289    fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
290
291    /// Sets the screen edges for which the system gestures will take a lower priority than the
292    /// application's touch handling.
293    ///
294    /// This sets the initial value returned by
295    /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
296    ///
297    /// This only has an effect on iOS 11.0+.
298    fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
299
300    /// Sets whether the [`Window`] prefers the status bar hidden.
301    ///
302    /// The default is to prefer showing the status bar.
303    ///
304    /// This sets the initial value returned by
305    /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
306    fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
307
308    /// Sets the style of the [`Window`]'s status bar.
309    ///
310    /// The default is system-defined.
311    ///
312    /// This sets the initial value returned by
313    /// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
314    fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
315}
316
317impl WindowAttributesExtIOS for WindowAttributes {
318    #[inline]
319    fn with_scale_factor(mut self, scale_factor: f64) -> Self {
320        self.platform_specific.scale_factor = Some(scale_factor);
321        self
322    }
323
324    #[inline]
325    fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
326        self.platform_specific.valid_orientations = valid_orientations;
327        self
328    }
329
330    #[inline]
331    fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
332        self.platform_specific.prefers_home_indicator_hidden = hidden;
333        self
334    }
335
336    #[inline]
337    fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
338        self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges;
339        self
340    }
341
342    #[inline]
343    fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
344        self.platform_specific.prefers_status_bar_hidden = hidden;
345        self
346    }
347
348    #[inline]
349    fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
350        self.platform_specific.preferred_status_bar_style = status_bar_style;
351        self
352    }
353}
354
355/// Additional methods on [`MonitorHandle`] that are specific to iOS.
356pub trait MonitorHandleExtIOS {
357    /// Returns a pointer to the [`UIScreen`] that is used by this monitor.
358    ///
359    /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
360    fn ui_screen(&self) -> *mut c_void;
361
362    /// Returns the preferred [`VideoModeHandle`] for this monitor.
363    ///
364    /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
365    fn preferred_video_mode(&self) -> VideoModeHandle;
366}
367
368impl MonitorHandleExtIOS for MonitorHandle {
369    #[inline]
370    fn ui_screen(&self) -> *mut c_void {
371        // SAFETY: The marker is only used to get the pointer of the screen
372        let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
373        objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
374    }
375
376    #[inline]
377    fn preferred_video_mode(&self) -> VideoModeHandle {
378        VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
379    }
380}
381
382/// Valid orientations for a particular [`Window`].
383#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
384pub enum ValidOrientations {
385    /// Excludes `PortraitUpsideDown` on iphone
386    #[default]
387    LandscapeAndPortrait,
388
389    Landscape,
390
391    /// Excludes `PortraitUpsideDown` on iphone
392    Portrait,
393}
394
395/// The device [idiom].
396///
397/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
398#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
399pub enum Idiom {
400    Unspecified,
401
402    /// iPhone and iPod touch.
403    Phone,
404
405    /// iPad.
406    Pad,
407
408    /// tvOS and Apple TV.
409    TV,
410    CarPlay,
411}
412
413bitflags::bitflags! {
414    /// The [edges] of a screen.
415    ///
416    /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
417    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
418    pub struct ScreenEdge: u8 {
419        const NONE   = 0;
420        const TOP    = 1 << 0;
421        const LEFT   = 1 << 1;
422        const BOTTOM = 1 << 2;
423        const RIGHT  = 1 << 3;
424        const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
425            | ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
426    }
427}
428
429#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
430pub enum StatusBarStyle {
431    #[default]
432    Default,
433    LightContent,
434    DarkContent,
435}