Skip to content

Commit 80e6d67

Browse files
radexfacebook-github-bot
authored andcommitted
UIViewController-based status bar management (#25919)
Summary: {emoji:26a0} This is a follow up to #25425 -- which isn't merged yet… See https://github.com/facebook/react-native/pull/25919/files/2a286257a6553a80a34e2b1f1ad94fc7bae36ea3..125aedbedc234c65c8d1b2133b79e926ad6cf145 for actual diff Currently, StatusBar native module manages the status bar on iOS globally, using `UIApplication.` APIs. This is bad because: - those APIs have been deprecated for 4 years - Apple really, really wants you to have an explicitly defined view controller, and control the status bar there - it [breaks external native components](#25181 (comment)) - it's [not compatible with iPadOS 13 multi window support](#25181 (comment)) for those reasons I we should transition towards view controller-based status bar management. With that, there is a need to introduce a default React Native root view controller, so I added `RCTRootViewController`. Using it is completely opt-in and there is no breaking change here. However I believe this should be a part of the template for new RN iOS apps. Additionally, I added `RCTRootViewControllerProtocol` with hooks needed for RCTStatusBarManager to control the status bar. This means apps that want to have total control over their view controller can still opt in to react native VC-based status bar by conforming their root view controller to this protocol. ## Changelog [iOS] [Added] - Added `RCTRootViewController` and `RCTRootViewControllerProtocol` [iOS] [Fixed] - `UIViewControllerBasedStatusBarAppearance=YES` no longer triggers an error as long as you use `RCTRootViewController` [iOS] [Fixed] - Status bar style is now correctly changed in multi-window iPadOS 13 apps if you use `RCTRootViewController` and set `UIViewControllerBasedStatusBarAppearance=YES` Pull Request resolved: #25919 Test Plan: - Open RNTester → StatusBar → and check that no features broke Reviewed By: fkgozali Differential Revision: D16957766 Pulled By: hramos fbshipit-source-id: 9ae1384ee20a06933053c4404b8237810f1e7c2c
1 parent b58e176 commit 80e6d67

File tree

7 files changed

+177
-28
lines changed

7 files changed

+177
-28
lines changed

RNTester/RNTester/AppDelegate.mm

+5-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#import <React/RCTDataRequestHandler.h>
2222
#import <React/RCTFileRequestHandler.h>
2323
#import <React/RCTRootView.h>
24+
#import <React/RCTRootViewController.h>
2425
#import <ReactCommon/BridgeJSCallInvoker.h>
2526

2627
#import <cxxreact/JSExecutor.h>
@@ -89,12 +90,13 @@ - (BOOL)application:(__unused UIApplication *)application didFinishLaunchingWith
8990

9091
UIView *rootView = [[RCTFabricSurfaceHostingProxyRootView alloc] initWithBridge:_bridge moduleName:@"RNTesterApp" initialProperties:initProps];
9192
#else
92-
UIView *rootView = [[RCTRootView alloc] initWithBridge:_bridge moduleName:@"RNTesterApp" initialProperties:initProps];
93+
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge
94+
moduleName:@"RNTesterApp"
95+
initialProperties:initProps];
9396
#endif
9497

9598
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
96-
UIViewController *rootViewController = [UIViewController new];
97-
rootViewController.view = rootView;
99+
RCTRootViewController *rootViewController = [[RCTRootViewController alloc] initWithRootView:rootView];
98100
self.window.rootViewController = rootViewController;
99101
[self.window makeKeyAndVisible];
100102
[self initializeFlipper:application];

RNTester/RNTester/Info.plist

-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,6 @@
5656
<string>UIInterfaceOrientationLandscapeLeft</string>
5757
<string>UIInterfaceOrientationLandscapeRight</string>
5858
</array>
59-
<key>UIViewControllerBasedStatusBarAppearance</key>
60-
<false/>
6159
<key>NSPhotoLibraryUsageDescription</key>
6260
<string>You need to add NSPhotoLibraryUsageDescription key in Info.plist to enable photo library usage, otherwise it is going to *fail silently*!</string>
6361
<key>RN_BUNDLE_PREFIX</key>

React/Base/RCTRootViewController.h

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <UIKit/UIKit.h>
9+
10+
#import <React/RCTBridge.h>
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
@class RCTRootView;
15+
16+
@protocol RCTRootViewControllerProtocol <NSObject>
17+
18+
/**
19+
* RCTStatusBarManager calls this to update the status bar style.
20+
*
21+
* Conforming view controllers should use this to update preferred status bar style
22+
*/
23+
- (void)updateStatusBarStyle:(UIStatusBarStyle)style
24+
hidden:(BOOL)hidden
25+
animation:(UIStatusBarAnimation)animation
26+
animated:(BOOL)animate;
27+
28+
@end
29+
30+
@interface RCTRootViewController : UIViewController <RCTRootViewControllerProtocol>
31+
32+
/**
33+
* - Designated initializer -
34+
*/
35+
- (instancetype)initWithRootView:(RCTRootView *)rootView NS_DESIGNATED_INITIALIZER;
36+
37+
/**
38+
* The root view used by the view controller.
39+
*/
40+
@property (nonatomic, strong, readonly) RCTRootView *rootView;
41+
42+
/**
43+
* See: RCTRootViewControllerProtocol
44+
*/
45+
- (void)updateStatusBarStyle:(UIStatusBarStyle)style
46+
hidden:(BOOL)hidden
47+
animation:(UIStatusBarAnimation)animation
48+
animated:(BOOL)animate;
49+
50+
@end
51+
52+
NS_ASSUME_NONNULL_END

React/Base/RCTRootViewController.m

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTRootViewController.h"
9+
#import "RCTUtils.h"
10+
#import "RCTRootView.h"
11+
12+
@implementation RCTRootViewController
13+
{
14+
UIStatusBarStyle _statusBarStyle;
15+
BOOL _statusBarHidden;
16+
UIStatusBarAnimation _statusBarAnimation;
17+
}
18+
19+
- (instancetype)initWithRootView:(RCTRootView *)rootView
20+
{
21+
RCTAssertParam(rootView);
22+
23+
if (self = [super initWithNibName:nil bundle:nil]) {
24+
_rootView = rootView;
25+
_statusBarStyle = UIStatusBarStyleDefault;
26+
_statusBarHidden = false;
27+
_statusBarAnimation = UIStatusBarAnimationFade;
28+
}
29+
30+
return self;
31+
}
32+
33+
RCT_NOT_IMPLEMENTED(- (instancetype)initWithNibName:(NSString *)nn bundle:(NSBundle *)nb)
34+
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder)
35+
36+
- (void)loadView
37+
{
38+
self.view = _rootView;
39+
}
40+
41+
- (UIStatusBarStyle)preferredStatusBarStyle
42+
{
43+
return _statusBarStyle;
44+
}
45+
46+
- (BOOL)prefersStatusBarHidden
47+
{
48+
return _statusBarHidden;
49+
}
50+
51+
- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation
52+
{
53+
return _statusBarAnimation;
54+
}
55+
56+
- (void)updateStatusBarStyle:(UIStatusBarStyle)style
57+
hidden:(BOOL)hidden
58+
animation:(UIStatusBarAnimation)animation
59+
animated:(BOOL)animate;
60+
{
61+
_statusBarStyle = style;
62+
_statusBarHidden = hidden;
63+
_statusBarAnimation = animation;
64+
if (animate) {
65+
[UIView animateWithDuration:0.150 animations:^{
66+
[self setNeedsStatusBarAppearanceUpdate];
67+
}];
68+
} else {
69+
[self setNeedsStatusBarAppearanceUpdate];
70+
}
71+
}
72+
73+
@end

React/CoreModules/RCTStatusBarManager.mm

+45-19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#import <React/RCTLog.h>
1414
#import <React/RCTUIManager.h>
1515
#import <React/RCTUtils.h>
16+
#import <React/RCTRootViewController.h>
1617

1718
#if !TARGET_OS_TV
1819
#import <FBReactNativeSpec/FBReactNativeSpec.h>
@@ -144,7 +145,27 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification
144145
[self emitEvent:@"statusBarFrameWillChange" forNotification:notification];
145146
}
146147

147-
RCT_EXPORT_METHOD(getHeight : (RCTResponseSenderBlock)callback)
148+
- (UIViewController<RCTRootViewControllerProtocol>*) viewControllerForReactTag:(nonnull NSNumber *)reactTag
149+
{
150+
if (!RCTViewControllerBasedStatusBarAppearance()) {
151+
return nil;
152+
}
153+
154+
UIView *view = [self.bridge.uiManager viewForReactTag:reactTag];
155+
UIViewController *viewController = view.window.rootViewController ?: RCTKeyWindow().rootViewController;
156+
157+
if ([viewController conformsToProtocol:@protocol(RCTRootViewControllerProtocol)]) {
158+
return (UIViewController<RCTRootViewControllerProtocol>*) viewController;
159+
} else {
160+
RCTLogError(@"RCTStatusBarManager could not find RCTRootViewController. \
161+
If UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to YES (recommended for new apps), \
162+
You need to use RCTRootViewControllerProtocol-conforming view controller as app window's root view controller \
163+
and must pass a node reference to `surface` argument of StatusBar methods.");
164+
return nil;
165+
}
166+
}
167+
168+
RCT_EXPORT_METHOD(getHeight:(RCTResponseSenderBlock)callback)
148169
{
149170
callback(@[ @{
150171
@"height" : @(RCTSharedApplication().statusBarFrame.size.height),
@@ -155,38 +176,43 @@ - (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification
155176
animated:(BOOL)animated
156177
reactTag:(double)reactTag)
157178
{
158-
UIStatusBarStyle statusBarStyle = [RCTConvert UIStatusBarStyle:style];
159-
if (RCTViewControllerBasedStatusBarAppearance()) {
160-
RCTLogError(@"RCTStatusBarManager module requires that the \
161-
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
162-
return;
163-
}
179+
// NSNumber *reactTag = options.reactTag() ? @(options.reactTag()) : @-1;
180+
UIStatusBarStyle statusBarStyle = [RCTConvert UIStatusBarStyle:style];
181+
UIViewController<RCTRootViewControllerProtocol> *viewController = [self viewControllerForReactTag:@(reactTag)];
164182

165-
// TODO (T62270453): Add proper support for UIScenes (this requires view controller based status bar management)
183+
if (viewController) {
184+
[viewController updateStatusBarStyle:statusBarStyle
185+
hidden:viewController.prefersStatusBarHidden
186+
animation:viewController.preferredStatusBarUpdateAnimation
187+
animated:animated];
188+
} else {
166189
#pragma clang diagnostic push
167190
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
168-
[RCTSharedApplication() setStatusBarStyle:statusBarStyle
169-
animated:animated];
191+
[RCTSharedApplication() setStatusBarStyle:statusBarStyle
192+
animated:animated];
170193
#pragma clang diagnostic pop
194+
}
171195
}
172196

173197
RCT_EXPORT_METHOD(setHidden:(BOOL)hidden
174198
withAnimation:(NSString *)withAnimation
175199
reactTag:(double)reactTag)
176200
{
177201
UIStatusBarAnimation animation = [RCTConvert UIStatusBarAnimation:withAnimation];
178-
if (RCTViewControllerBasedStatusBarAppearance()) {
179-
RCTLogError(@"RCTStatusBarManager module requires that the \
180-
UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
181-
return;
182-
}
183-
184-
// TODO (T62270453): Add proper support for UIScenes (this requires view controller based status bar management)
202+
UIViewController<RCTRootViewControllerProtocol> *viewController = [self viewControllerForReactTag:@(reactTag)];
203+
204+
if (viewController) {
205+
[viewController updateStatusBarStyle:viewController.preferredStatusBarStyle
206+
hidden:hidden
207+
animation:animation
208+
animated:animation != UIStatusBarAnimationNone];
209+
} else {
185210
#pragma clang diagnostic push
186211
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
187-
[RCTSharedApplication() setStatusBarHidden:hidden
188-
withAnimation:animation];
212+
[RCTSharedApplication() setStatusBarHidden:hidden
213+
withAnimation:animation];
189214
#pragma clang diagnostic pop
215+
}
190216
}
191217

192218
RCT_EXPORT_METHOD(setNetworkActivityIndicatorVisible : (BOOL)visible)

template/ios/HelloWorld/AppDelegate.m

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#import <React/RCTBridge.h>
44
#import <React/RCTBundleURLProvider.h>
55
#import <React/RCTRootView.h>
6+
#import <React/RCTRootViewController.h>
67

78
#if DEBUG
89
#import <FlipperKit/FlipperClient.h>
@@ -39,8 +40,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
3940
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
4041

4142
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
42-
UIViewController *rootViewController = [UIViewController new];
43-
rootViewController.view = rootView;
43+
RCTRootViewController *rootViewController = [[RCTRootViewController alloc] initWithRootView:rootView];
4444
self.window.rootViewController = rootViewController;
4545
[self.window makeKeyAndVisible];
4646
return YES;

template/ios/HelloWorld/Info.plist

-2
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,5 @@
5151
<string>UIInterfaceOrientationLandscapeLeft</string>
5252
<string>UIInterfaceOrientationLandscapeRight</string>
5353
</array>
54-
<key>UIViewControllerBasedStatusBarAppearance</key>
55-
<false/>
5654
</dict>
5755
</plist>

0 commit comments

Comments
 (0)