UNPKG

9.62 kBPlain TextView Raw
1#import "RNSScreenWindowTraits.h"
2#import "RNSScreenContainer.h"
3#import "RNSScreenStack.h"
4
5@implementation RNSScreenWindowTraits
6
7#if !TARGET_OS_TV && !TARGET_OS_VISION
8+ (void)assertViewControllerBasedStatusBarAppearenceSet
9{
10 static dispatch_once_t once;
11 static bool viewControllerBasedAppearence;
12 dispatch_once(&once, ^{
13 viewControllerBasedAppearence =
14 [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"] boolValue];
15 });
16 if (!viewControllerBasedAppearence) {
17 RCTLogError(@"If you want to change the appearance of status bar, you have to change \
18 UIViewControllerBasedStatusBarAppearance key in the Info.plist to YES");
19 }
20}
21#endif
22
23+ (void)updateStatusBarAppearance
24{
25#if !TARGET_OS_TV && !TARGET_OS_VISION
26 [UIView animateWithDuration:0.4
27 animations:^{ // duration based on "Programming iOS 13" p. 311 implementation
28 [RCTKeyWindow().rootViewController setNeedsStatusBarAppearanceUpdate];
29 }];
30#endif
31}
32
33+ (void)updateHomeIndicatorAutoHidden
34{
35#if !TARGET_OS_TV
36 [RCTKeyWindow().rootViewController setNeedsUpdateOfHomeIndicatorAutoHidden];
37#endif
38}
39
40#if !TARGET_OS_TV
41+ (UIStatusBarStyle)statusBarStyleForRNSStatusBarStyle:(RNSStatusBarStyle)statusBarStyle
42{
43#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
44 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
45 if (@available(iOS 13.0, *)) {
46 switch (statusBarStyle) {
47 case RNSStatusBarStyleAuto:
48 return [UITraitCollection.currentTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark
49 ? UIStatusBarStyleLightContent
50 : UIStatusBarStyleDarkContent;
51 case RNSStatusBarStyleInverted:
52 return [UITraitCollection.currentTraitCollection userInterfaceStyle] == UIUserInterfaceStyleDark
53 ? UIStatusBarStyleDarkContent
54 : UIStatusBarStyleLightContent;
55 case RNSStatusBarStyleLight:
56 return UIStatusBarStyleLightContent;
57 case RNSStatusBarStyleDark:
58 return UIStatusBarStyleDarkContent;
59 default:
60 return UIStatusBarStyleLightContent;
61 }
62 }
63#endif
64 // it is the only non-default style available for iOS < 13
65 if (statusBarStyle == RNSStatusBarStyleLight) {
66 return UIStatusBarStyleLightContent;
67 }
68 return UIStatusBarStyleDefault;
69}
70#endif
71
72#if !TARGET_OS_TV
73+ (UIInterfaceOrientation)defaultOrientationForOrientationMask:(UIInterfaceOrientationMask)orientationMask
74{
75 if (UIInterfaceOrientationMaskPortrait & orientationMask) {
76 return UIInterfaceOrientationPortrait;
77 } else if (UIInterfaceOrientationMaskLandscapeLeft & orientationMask) {
78 return UIInterfaceOrientationLandscapeLeft;
79 } else if (UIInterfaceOrientationMaskLandscapeRight & orientationMask) {
80 return UIInterfaceOrientationLandscapeRight;
81 } else if (UIInterfaceOrientationMaskPortraitUpsideDown & orientationMask) {
82 return UIInterfaceOrientationPortraitUpsideDown;
83 }
84 return UIInterfaceOrientationUnknown;
85}
86
87+ (UIInterfaceOrientation)interfaceOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
88{
89 switch (deviceOrientation) {
90 case UIDeviceOrientationPortrait:
91 return UIInterfaceOrientationPortrait;
92 case UIDeviceOrientationPortraitUpsideDown:
93 return UIInterfaceOrientationPortraitUpsideDown;
94 // UIDevice and UIInterface landscape orientations are switched
95 case UIDeviceOrientationLandscapeLeft:
96 return UIInterfaceOrientationLandscapeRight;
97 case UIDeviceOrientationLandscapeRight:
98 return UIInterfaceOrientationLandscapeLeft;
99 default:
100 return UIInterfaceOrientationUnknown;
101 }
102}
103
104+ (UIInterfaceOrientationMask)maskFromOrientation:(UIInterfaceOrientation)orientation
105{
106 return 1 << orientation;
107}
108#endif
109
110+ (void)enforceDesiredDeviceOrientation
111{
112#if !TARGET_OS_TV && !TARGET_OS_VISION
113 dispatch_async(dispatch_get_main_queue(), ^{
114 UIInterfaceOrientationMask orientationMask = [RCTKeyWindow().rootViewController supportedInterfaceOrientations];
115
116 UIInterfaceOrientation currentDeviceOrientation =
117 [RNSScreenWindowTraits interfaceOrientationFromDeviceOrientation:[[UIDevice currentDevice] orientation]];
118 UIInterfaceOrientation currentInterfaceOrientation = [RNSScreenWindowTraits interfaceOrientation];
119 UIInterfaceOrientation newOrientation = UIInterfaceOrientationUnknown;
120 if ([RNSScreenWindowTraits maskFromOrientation:currentDeviceOrientation] & orientationMask) {
121 if (!([RNSScreenWindowTraits maskFromOrientation:currentInterfaceOrientation] & orientationMask)) {
122 // if the device orientation is in the mask, but interface orientation is not, we rotate to device's orientation
123 newOrientation = currentDeviceOrientation;
124 } else {
125 if (currentDeviceOrientation != currentInterfaceOrientation) {
126 // if both device orientation and interface orientation are in the mask, but in different orientations, we
127 // rotate to device's orientation
128 newOrientation = currentDeviceOrientation;
129 }
130 }
131 } else {
132 if (!([RNSScreenWindowTraits maskFromOrientation:currentInterfaceOrientation] & orientationMask)) {
133 // if both device orientation and interface orientation are not in the mask, we rotate to closest available
134 // rotation from mask
135 newOrientation = [RNSScreenWindowTraits defaultOrientationForOrientationMask:orientationMask];
136 } else {
137 // if the device orientation is not in the mask, but interface orientation is in the mask, do nothing
138 }
139 }
140 if (newOrientation != UIInterfaceOrientationUnknown) {
141#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_16_0) && \
142 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0
143 if (@available(iOS 16.0, *)) {
144 NSArray *array = [[[UIApplication sharedApplication] connectedScenes] allObjects];
145
146 // when an app supports multiple scenes (e.g. CarPlay), it is possible that
147 // UIWindowScene is not the first scene, or it may not be present at all
148 UIWindowScene *scene = nil;
149 for (id connectedScene in array) {
150 if ([connectedScene isKindOfClass:[UIWindowScene class]]) {
151 scene = connectedScene;
152 break;
153 }
154 }
155
156 if (scene == nil) {
157 return;
158 }
159
160 UIWindowSceneGeometryPreferencesIOS *geometryPreferences =
161 [[UIWindowSceneGeometryPreferencesIOS alloc] initWithInterfaceOrientations:orientationMask];
162 [scene requestGeometryUpdateWithPreferences:geometryPreferences
163 errorHandler:^(NSError *_Nonnull error){
164 }];
165
166 // `attemptRotationToDeviceOrientation` is deprecated for modern OS versions
167 // so we need to use `setNeedsUpdateOfSupportedInterfaceOrientations`
168 UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
169 while (topController.presentedViewController) {
170 topController = topController.presentedViewController;
171 }
172
173 [topController setNeedsUpdateOfSupportedInterfaceOrientations];
174 } else
175#endif // Check for iOS 16
176 {
177 [[UIDevice currentDevice] setValue:@(newOrientation) forKey:@"orientation"];
178 [UIViewController attemptRotationToDeviceOrientation];
179 }
180 }
181 });
182#endif // !TARGET_TV_OS
183}
184
185+ (void)updateWindowTraits
186{
187 [RNSScreenWindowTraits updateStatusBarAppearance];
188 [RNSScreenWindowTraits enforceDesiredDeviceOrientation];
189 [RNSScreenWindowTraits updateHomeIndicatorAutoHidden];
190}
191
192#if !TARGET_OS_TV && !TARGET_OS_VISION
193// based on
194// https://stackoverflow.com/questions/57965701/statusbarorientation-was-deprecated-in-ios-13-0-when-attempting-to-get-app-ori/61249908#61249908
195+ (UIInterfaceOrientation)interfaceOrientation
196{
197#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
198 __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
199 if (@available(iOS 13.0, *)) {
200 UIWindowScene *windowScene = RCTKeyWindow().windowScene;
201 if (windowScene == nil) {
202 return UIInterfaceOrientationUnknown;
203 }
204 return windowScene.interfaceOrientation;
205 } else
206#endif
207 {
208 return UIApplication.sharedApplication.statusBarOrientation;
209 }
210}
211#endif
212
213// method to be used in Expo for checking if RNScreens have trait set
214+ (BOOL)shouldAskScreensForTrait:(RNSWindowTrait)trait
215 includingModals:(BOOL)includingModals
216 inViewController:(UIViewController *)vc
217{
218 UIViewController *lastViewController = [[vc childViewControllers] lastObject];
219 if ([lastViewController conformsToProtocol:@protocol(RNSViewControllerDelegate)]) {
220 UIViewController *vc = nil;
221 if ([lastViewController isKindOfClass:[RNSViewController class]]) {
222 vc = [(RNSViewController *)lastViewController findActiveChildVC];
223 } else if ([lastViewController isKindOfClass:[RNSNavigationController class]]) {
224 vc = [(RNSNavigationController *)lastViewController topViewController];
225 }
226 return [vc isKindOfClass:[RNSScreen class]] &&
227 [(RNSScreen *)vc findChildVCForConfigAndTrait:trait includingModals:includingModals] != nil;
228 }
229 return NO;
230}
231
232// same method as above, but directly for orientation
233+ (BOOL)shouldAskScreensForScreenOrientationInViewController:(UIViewController *)vc
234{
235 return [RNSScreenWindowTraits shouldAskScreensForTrait:RNSWindowTraitOrientation
236 includingModals:YES
237 inViewController:vc];
238}
239
240@end