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}