diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm index 30eca35571e..f6fb36f3aa3 100644 --- a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm +++ b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm @@ -8,6 +8,7 @@ #import "RCTInitializeUIKitProxies.h" #import "RCTInitialAccessibilityValuesProxy.h" #import "RCTInitialAppStateProxy.h" +#import "RCTKeyWindowValuesProxy.h" #import "RCTTraitCollectionProxy.h" #import "RCTWindowSafeAreaProxy.h" @@ -19,5 +20,6 @@ void RCTInitializeUIKitProxies(void) [[RCTTraitCollectionProxy sharedInstance] startObservingTraitCollection]; [[RCTInitialAppStateProxy sharedInstance] recordAppState]; [[RCTInitialAccessibilityValuesProxy sharedInstance] recordAccessibilityValues]; + [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary]; }); } diff --git a/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h new file mode 100644 index 00000000000..0723389c5b0 --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTKeyWindowValuesProxy : NSObject + ++ (instancetype)sharedInstance; + +@property (assign, readonly) CGSize windowSize; +@property (assign, readonly) UIInterfaceOrientation currentInterfaceOrientation; + +- (void)startObservingWindowSizeIfNecessary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm new file mode 100644 index 00000000000..a53c0f9de06 --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTKeyWindowValuesProxy.mm @@ -0,0 +1,116 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import "RCTKeyWindowValuesProxy.h" +#import +#import +#import + +#import + +static NSString *const kFrameKeyPath = @"frame"; + +@implementation RCTKeyWindowValuesProxy { + BOOL _isObserving; + std::mutex _mutex; + CGSize _currentWindowSize; + UIInterfaceOrientation _currentInterfaceOrientation; +} + ++ (instancetype)sharedInstance +{ + static RCTKeyWindowValuesProxy *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [RCTKeyWindowValuesProxy new]; + }); + return sharedInstance; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _isObserving = NO; + UIView *mainWindow = RCTKeyWindow(); + _currentWindowSize = mainWindow ? mainWindow.bounds.size : UIScreen.mainScreen.bounds.size; + } + return self; +} + +- (void)startObservingWindowSizeIfNecessary +{ + std::lock_guard lock(_mutex); + if (!_isObserving) { + _isObserving = YES; + // For backwards compatibility, we register for notifications from the main thread only. + // On the new architecture, we are already on the main thread and RCTUnsafeExecuteOnMainQueueSync will simply call + // the block. + RCTUnsafeExecuteOnMainQueueSync(^{ + [RCTKeyWindow() addObserver:self forKeyPath:kFrameKeyPath options:NSKeyValueObservingOptionNew context:nil]; + }); + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_interfaceOrientationDidChange) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ + if ([keyPath isEqualToString:kFrameKeyPath]) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self]; + { + std::lock_guard lock(_mutex); + _currentWindowSize = RCTKeyWindow().bounds.size; + } + } +} + +- (CGSize)windowSize +{ + { + std::lock_guard lock(_mutex); + if (_isObserving) { + return _currentWindowSize; + } + } + + __block CGSize size; + RCTUnsafeExecuteOnMainQueueSync(^{ + size = RCTKeyWindow().bounds.size; + }); + return size; +} + +- (UIInterfaceOrientation)currentInterfaceOrientation +{ + { + std::lock_guard lock(_mutex); + if (_isObserving) { + return _currentInterfaceOrientation; + } + } + + __block UIInterfaceOrientation interfaceOrientation; + RCTUnsafeExecuteOnMainQueueSync(^{ + interfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; + }); + return interfaceOrientation; +} + +- (void)_interfaceOrientationDidChange +{ + std::lock_guard lock(_mutex); + _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; +} + +@end diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index 193e97448de..101438a0668 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -14,7 +14,9 @@ #import #import #import +#import #import +#import #import #import "CoreModulesPlugins.h" @@ -40,25 +42,14 @@ RCT_EXPORT_MODULE() - (instancetype)init { if (self = [super init]) { - [RCTKeyWindow() addObserver:self forKeyPath:kFrameKeyPath options:NSKeyValueObservingOptionNew context:nil]; + [[RCTKeyWindowValuesProxy sharedInstance] startObservingWindowSizeIfNecessary]; } return self; } -- (void)observeValueForKeyPath:(NSString *)keyPath - ofObject:(id)object - change:(NSDictionary *)change - context:(void *)context -{ - if ([keyPath isEqualToString:kFrameKeyPath]) { - [self interfaceFrameDidChange]; - [[NSNotificationCenter defaultCenter] postNotificationName:RCTWindowFrameDidChangeNotification object:self]; - } -} - + (BOOL)requiresMainQueueSetup { - return YES; + return NO; } - (dispatch_queue_t)methodQueue @@ -92,7 +83,7 @@ RCT_EXPORT_MODULE() #if TARGET_OS_IOS - _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; + _currentInterfaceOrientation = [RCTKeyWindowValuesProxy sharedInstance].currentInterfaceOrientation; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(interfaceFrameDidChange) @@ -145,13 +136,8 @@ static BOOL RCTIsIPhoneNotched() #if TARGET_OS_IOS dispatch_once(&onceToken, ^{ - RCTAssertMainQueue(); - // 20pt is the top safeArea value in non-notched devices - UIWindow *keyWindow = RCTKeyWindow(); - if (keyWindow) { - isIPhoneNotched = keyWindow.safeAreaInsets.top > 20; - } + isIPhoneNotched = [RCTWindowSafeAreaProxy sharedInstance].currentSafeAreaInsets.top > 20; }); #endif @@ -160,13 +146,11 @@ static BOOL RCTIsIPhoneNotched() static NSDictionary *RCTExportedDimensions(CGFloat fontScale) { - RCTAssertMainQueue(); UIScreen *mainScreen = UIScreen.mainScreen; CGSize screenSize = mainScreen.bounds.size; - UIView *mainWindow = RCTKeyWindow(); // We fallback to screen size if a key window is not found. - CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize; + CGSize windowSize = [RCTKeyWindowValuesProxy sharedInstance].windowSize; NSDictionary *dimsWindow = @{ @"width" : @(windowSize.width), @@ -202,20 +186,14 @@ static NSDictionary *RCTExportedDimensions(CGFloat fontScale) - (NSDictionary *)getConstants { - __block NSDictionary *constants; - __weak __typeof(self) weakSelf = self; - RCTUnsafeExecuteOnMainQueueSync(^{ - constants = @{ - @"Dimensions" : [weakSelf _exportedDimensions], - // Note: - // This prop is deprecated and will be removed in a future release. - // Please use this only for a quick and temporary solution. - // Use instead. - @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()), - }; - }); - - return constants; + return @{ + @"Dimensions" : [self _exportedDimensions], + // Note: + // This prop is deprecated and will be removed in a future release. + // Please use this only for a quick and temporary solution. + // Use instead. + @"isIPhoneX_deprecated" : @(RCTIsIPhoneNotched()), + }; } - (void)didReceiveNewContentSizeMultiplier