diff --git a/RNTester/RNTester/AppDelegate.mm b/RNTester/RNTester/AppDelegate.mm index 137ab78d97..22cf56a33b 100644 --- a/RNTester/RNTester/AppDelegate.mm +++ b/RNTester/RNTester/AppDelegate.mm @@ -21,6 +21,7 @@ #import #import #import +#import #import #import @@ -89,12 +90,13 @@ UIView *rootView = [[RCTFabricSurfaceHostingProxyRootView alloc] initWithBridge:_bridge moduleName:@"RNTesterApp" initialProperties:initProps]; #else - UIView *rootView = [[RCTRootView alloc] initWithBridge:_bridge moduleName:@"RNTesterApp" initialProperties:initProps]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:_bridge + moduleName:@"RNTesterApp" + initialProperties:initProps]; #endif self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [UIViewController new]; - rootViewController.view = rootView; + RCTRootViewController *rootViewController = [[RCTRootViewController alloc] initWithRootView:rootView]; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; [self initializeFlipper:application]; diff --git a/RNTester/RNTester/Info.plist b/RNTester/RNTester/Info.plist index 3b0806d0b9..6a14cb7619 100644 --- a/RNTester/RNTester/Info.plist +++ b/RNTester/RNTester/Info.plist @@ -56,8 +56,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance - NSPhotoLibraryUsageDescription You need to add NSPhotoLibraryUsageDescription key in Info.plist to enable photo library usage, otherwise it is going to *fail silently*! RN_BUNDLE_PREFIX diff --git a/React/Base/RCTRootViewController.h b/React/Base/RCTRootViewController.h new file mode 100644 index 0000000000..9ed1d1c924 --- /dev/null +++ b/React/Base/RCTRootViewController.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class RCTRootView; + +@protocol RCTRootViewControllerProtocol + +/** + * RCTStatusBarManager calls this to update the status bar style. + * + * Conforming view controllers should use this to update preferred status bar style + */ +- (void)updateStatusBarStyle:(UIStatusBarStyle)style + hidden:(BOOL)hidden + animation:(UIStatusBarAnimation)animation + animated:(BOOL)animate; + +@end + +@interface RCTRootViewController : UIViewController + +/** + * - Designated initializer - + */ +- (instancetype)initWithRootView:(RCTRootView *)rootView NS_DESIGNATED_INITIALIZER; + +/** + * The root view used by the view controller. + */ +@property (nonatomic, strong, readonly) RCTRootView *rootView; + +/** + * See: RCTRootViewControllerProtocol + */ +- (void)updateStatusBarStyle:(UIStatusBarStyle)style + hidden:(BOOL)hidden + animation:(UIStatusBarAnimation)animation + animated:(BOOL)animate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/React/Base/RCTRootViewController.m b/React/Base/RCTRootViewController.m new file mode 100644 index 0000000000..96f61c82ea --- /dev/null +++ b/React/Base/RCTRootViewController.m @@ -0,0 +1,73 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTRootViewController.h" +#import "RCTUtils.h" +#import "RCTRootView.h" + +@implementation RCTRootViewController +{ + UIStatusBarStyle _statusBarStyle; + BOOL _statusBarHidden; + UIStatusBarAnimation _statusBarAnimation; +} + +- (instancetype)initWithRootView:(RCTRootView *)rootView +{ + RCTAssertParam(rootView); + + if (self = [super initWithNibName:nil bundle:nil]) { + _rootView = rootView; + _statusBarStyle = UIStatusBarStyleDefault; + _statusBarHidden = false; + _statusBarAnimation = UIStatusBarAnimationFade; + } + + return self; +} + +RCT_NOT_IMPLEMENTED(- (instancetype)initWithNibName:(NSString *)nn bundle:(NSBundle *)nb) +RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) + +- (void)loadView +{ + self.view = _rootView; +} + +- (UIStatusBarStyle)preferredStatusBarStyle +{ + return _statusBarStyle; +} + +- (BOOL)prefersStatusBarHidden +{ + return _statusBarHidden; +} + +- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation +{ + return _statusBarAnimation; +} + +- (void)updateStatusBarStyle:(UIStatusBarStyle)style + hidden:(BOOL)hidden + animation:(UIStatusBarAnimation)animation + animated:(BOOL)animate; +{ + _statusBarStyle = style; + _statusBarHidden = hidden; + _statusBarAnimation = animation; + if (animate) { + [UIView animateWithDuration:0.150 animations:^{ + [self setNeedsStatusBarAppearanceUpdate]; + }]; + } else { + [self setNeedsStatusBarAppearanceUpdate]; + } +} + +@end diff --git a/React/CoreModules/RCTStatusBarManager.mm b/React/CoreModules/RCTStatusBarManager.mm index b93805a426..53d550e7fe 100644 --- a/React/CoreModules/RCTStatusBarManager.mm +++ b/React/CoreModules/RCTStatusBarManager.mm @@ -13,6 +13,7 @@ #import #import #import +#import #if !TARGET_OS_TV #import @@ -144,7 +145,27 @@ RCT_EXPORT_MODULE() [self emitEvent:@"statusBarFrameWillChange" forNotification:notification]; } -RCT_EXPORT_METHOD(getHeight : (RCTResponseSenderBlock)callback) +- (UIViewController*) viewControllerForReactTag:(nonnull NSNumber *)reactTag +{ + if (!RCTViewControllerBasedStatusBarAppearance()) { + return nil; + } + + UIView *view = [self.bridge.uiManager viewForReactTag:reactTag]; + UIViewController *viewController = view.window.rootViewController ?: RCTKeyWindow().rootViewController; + + if ([viewController conformsToProtocol:@protocol(RCTRootViewControllerProtocol)]) { + return (UIViewController*) viewController; + } else { + RCTLogError(@"RCTStatusBarManager could not find RCTRootViewController. \ + If UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to YES (recommended for new apps), \ + You need to use RCTRootViewControllerProtocol-conforming view controller as app window's root view controller \ + and must pass a node reference to `surface` argument of StatusBar methods."); + return nil; + } +} + +RCT_EXPORT_METHOD(getHeight:(RCTResponseSenderBlock)callback) { callback(@[ @{ @"height" : @(RCTSharedApplication().statusBarFrame.size.height), @@ -155,19 +176,22 @@ RCT_EXPORT_METHOD(setStyle:(NSString *)style animated:(BOOL)animated reactTag:(double)reactTag) { - UIStatusBarStyle statusBarStyle = [RCTConvert UIStatusBarStyle:style]; - if (RCTViewControllerBasedStatusBarAppearance()) { - RCTLogError(@"RCTStatusBarManager module requires that the \ - UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - return; - } + // NSNumber *reactTag = options.reactTag() ? @(options.reactTag()) : @-1; + UIStatusBarStyle statusBarStyle = [RCTConvert UIStatusBarStyle:style]; + UIViewController *viewController = [self viewControllerForReactTag:@(reactTag)]; - // TODO (T62270453): Add proper support for UIScenes (this requires view controller based status bar management) + if (viewController) { + [viewController updateStatusBarStyle:statusBarStyle + hidden:viewController.prefersStatusBarHidden + animation:viewController.preferredStatusBarUpdateAnimation + animated:animated]; + } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [RCTSharedApplication() setStatusBarStyle:statusBarStyle - animated:animated]; + [RCTSharedApplication() setStatusBarStyle:statusBarStyle + animated:animated]; #pragma clang diagnostic pop + } } RCT_EXPORT_METHOD(setHidden:(BOOL)hidden @@ -175,18 +199,20 @@ RCT_EXPORT_METHOD(setHidden:(BOOL)hidden reactTag:(double)reactTag) { UIStatusBarAnimation animation = [RCTConvert UIStatusBarAnimation:withAnimation]; - if (RCTViewControllerBasedStatusBarAppearance()) { - RCTLogError(@"RCTStatusBarManager module requires that the \ - UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO"); - return; - } + UIViewController *viewController = [self viewControllerForReactTag:@(reactTag)]; - // TODO (T62270453): Add proper support for UIScenes (this requires view controller based status bar management) + if (viewController) { + [viewController updateStatusBarStyle:viewController.preferredStatusBarStyle + hidden:hidden + animation:animation + animated:animation != UIStatusBarAnimationNone]; + } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - [RCTSharedApplication() setStatusBarHidden:hidden - withAnimation:animation]; + [RCTSharedApplication() setStatusBarHidden:hidden + withAnimation:animation]; #pragma clang diagnostic pop + } } RCT_EXPORT_METHOD(setNetworkActivityIndicatorVisible : (BOOL)visible) diff --git a/template/ios/HelloWorld/AppDelegate.m b/template/ios/HelloWorld/AppDelegate.m index c680572d44..79d9e6083b 100644 --- a/template/ios/HelloWorld/AppDelegate.m +++ b/template/ios/HelloWorld/AppDelegate.m @@ -3,6 +3,7 @@ #import #import #import +#import #if DEBUG #import @@ -39,8 +40,7 @@ static void InitializeFlipper(UIApplication *application) { rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - UIViewController *rootViewController = [UIViewController new]; - rootViewController.view = rootView; + RCTRootViewController *rootViewController = [[RCTRootViewController alloc] initWithRootView:rootView]; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; return YES; diff --git a/template/ios/HelloWorld/Info.plist b/template/ios/HelloWorld/Info.plist index 20f7dd5114..fba9e97fb8 100644 --- a/template/ios/HelloWorld/Info.plist +++ b/template/ios/HelloWorld/Info.plist @@ -51,7 +51,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIViewControllerBasedStatusBarAppearance -