diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h new file mode 100644 index 00000000000000..c9f0dbf3585d91 --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.h @@ -0,0 +1,10 @@ +/* + * 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 + +void RCTInitializeUIKitProxies(void); diff --git a/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm new file mode 100644 index 00000000000000..74d28c92a66053 --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTInitializeUIKitProxies.mm @@ -0,0 +1,19 @@ +/* + * 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 "RCTInitializeUIKitProxies.h" +#import "RCTTraitCollectionProxy.h" +#import "RCTWindowSafeAreaProxy.h" + +void RCTInitializeUIKitProxies(void) +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [[RCTWindowSafeAreaProxy sharedInstance] startObservingSafeArea]; + [[RCTTraitCollectionProxy sharedInstance] startObservingTraitCollection]; + }); +} diff --git a/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h new file mode 100644 index 00000000000000..a92d9c5eb506af --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.h @@ -0,0 +1,26 @@ +/* + * 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 RCTTraitCollectionProxy : NSObject + ++ (instancetype)sharedInstance; + +/* + * Property to access the current trait collection. + * Thread safe. + */ +@property (nonatomic, readonly) UITraitCollection *currentTraitCollection; + +- (void)startObservingTraitCollection; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm new file mode 100644 index 00000000000000..69cf8aef11bee6 --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTTraitCollectionProxy.mm @@ -0,0 +1,80 @@ +/* + * 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 "RCTTraitCollectionProxy.h" +#import +#import + +#import + +@implementation RCTTraitCollectionProxy { + BOOL _isObserving; + std::mutex _mutex; + UITraitCollection *_currentTraitCollection; +} + ++ (instancetype)sharedInstance +{ + static RCTTraitCollectionProxy *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [RCTTraitCollectionProxy new]; + }); + return sharedInstance; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _isObserving = NO; + _currentTraitCollection = [RCTKeyWindow().traitCollection copy]; + } + return self; +} + +- (void)startObservingTraitCollection +{ + RCTAssertMainQueue(); + std::lock_guard lock(_mutex); + if (!_isObserving) { + _isObserving = YES; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_appearanceDidChange:) + name:RCTUserInterfaceStyleDidChangeNotification + object:nil]; + } +} + +- (UITraitCollection *)currentTraitCollection +{ + { + std::lock_guard lock(_mutex); + if (_isObserving) { + return _currentTraitCollection; + } + } + + // Fallback in case [RCTTraitCollectionProxy startObservingTraitCollection] was not called. + __block UITraitCollection *traitCollection = nil; + RCTUnsafeExecuteOnMainQueueSync(^{ + traitCollection = [RCTKeyWindow().traitCollection copy]; + }); + return traitCollection; +} + +- (void)_appearanceDidChange:(NSNotification *)notification +{ + std::lock_guard lock(_mutex); + + NSDictionary *userInfo = [notification userInfo]; + if (userInfo) { + _currentTraitCollection = userInfo[RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey]; + } +} + +@end diff --git a/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h b/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h new file mode 100644 index 00000000000000..043ef4271ffa85 --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.h @@ -0,0 +1,26 @@ +/* + * 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 RCTWindowSafeAreaProxy : NSObject + ++ (instancetype)sharedInstance; + +/* + * Property to access the current safe area insets of the window, read-only. + * Thread safe. + */ +@property (nonatomic, readonly) UIEdgeInsets currentSafeAreaInsets; + +- (void)startObservingSafeArea; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm b/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm new file mode 100644 index 00000000000000..20ce144d31957a --- /dev/null +++ b/packages/react-native/React/Base/UIKitProxies/RCTWindowSafeAreaProxy.mm @@ -0,0 +1,76 @@ +/* + * 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 "RCTWindowSafeAreaProxy.h" +#import +#import +#import + +#import + +@implementation RCTWindowSafeAreaProxy { + BOOL _isObserving; + std::mutex _currentSafeAreaInsetsMutex; + UIEdgeInsets _currentSafeAreaInsets; +} + ++ (instancetype)sharedInstance +{ + static RCTWindowSafeAreaProxy *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [RCTWindowSafeAreaProxy new]; + }); + return sharedInstance; +} + +- (instancetype)init +{ + self = [super init]; + if (self) { + _isObserving = NO; + _currentSafeAreaInsets = [UIApplication sharedApplication].delegate.window.safeAreaInsets; + } + return self; +} + +- (void)startObservingSafeArea +{ + RCTAssertMainQueue(); + std::lock_guard lock(_currentSafeAreaInsetsMutex); + if (!_isObserving) { + _isObserving = YES; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_interfaceFrameDidChange) + name:RCTUserInterfaceStyleDidChangeNotification + object:nil]; + } +} + +- (UIEdgeInsets)currentSafeAreaInsets +{ + std::lock_guard lock(_currentSafeAreaInsetsMutex); + + if (!_isObserving) { + // Fallback in case [startObservingSafeArea startObservingSafeArea] was not called. + __block UIEdgeInsets insets; + RCTUnsafeExecuteOnMainQueueSync(^{ + insets = [UIApplication sharedApplication].delegate.window.safeAreaInsets; + }); + return insets; + } + + return _currentSafeAreaInsets; +} + +- (void)_interfaceFrameDidChange +{ + std::lock_guard lock(_currentSafeAreaInsetsMutex); + _currentSafeAreaInsets = [UIApplication sharedApplication].delegate.window.safeAreaInsets; +} + +@end diff --git a/packages/react-native/React/CoreModules/RCTAppearance.mm b/packages/react-native/React/CoreModules/RCTAppearance.mm index 906cdf4d77d4b0..e9419d89315ee8 100644 --- a/packages/react-native/React/CoreModules/RCTAppearance.mm +++ b/packages/react-native/React/CoreModules/RCTAppearance.mm @@ -10,7 +10,7 @@ #import #import #import -#import +#import #import "CoreModulesPlugins.h" @@ -90,7 +90,7 @@ @implementation RCTAppearance { - (instancetype)init { if ((self = [super init])) { - UITraitCollection *traitCollection = RCTKeyWindow().traitCollection; + UITraitCollection *traitCollection = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection; _currentColorScheme = RCTColorSchemePreference(traitCollection); [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appearanceChanged:) @@ -104,7 +104,7 @@ - (instancetype)init + (BOOL)requiresMainQueueSetup { - return YES; + return NO; } - (dispatch_queue_t)methodQueue @@ -133,10 +133,7 @@ - (dispatch_queue_t)methodQueue RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getColorScheme) { if (!sIsAppearancePreferenceSet) { - __block UITraitCollection *traitCollection = nil; - RCTUnsafeExecuteOnMainQueueSync(^{ - traitCollection = RCTKeyWindow().traitCollection; - }); + UITraitCollection *traitCollection = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection; _currentColorScheme = RCTColorSchemePreference(traitCollection); } return _currentColorScheme; diff --git a/packages/react-native/React/CoreModules/RCTPlatform.mm b/packages/react-native/React/CoreModules/RCTPlatform.mm index ec8a5f3220e358..8ae71b1300722a 100644 --- a/packages/react-native/React/CoreModules/RCTPlatform.mm +++ b/packages/react-native/React/CoreModules/RCTPlatform.mm @@ -10,6 +10,7 @@ #import #import +#import #import #import @@ -48,7 +49,7 @@ @implementation RCTPlatform + (BOOL)requiresMainQueueSetup { - return YES; + return NO; } - (dispatch_queue_t)methodQueue @@ -64,30 +65,27 @@ - (dispatch_queue_t)methodQueue - (ModuleConstants)getConstants { - __block ModuleConstants constants; - RCTUnsafeExecuteOnMainQueueSync(^{ - UIDevice *device = [UIDevice currentDevice]; - auto versions = RCTGetReactNativeVersion(); - constants = typedConstants({ - .forceTouchAvailable = RCTForceTouchAvailable() ? true : false, - .osVersion = [device systemVersion], - .systemName = [device systemName], - .interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]), - .isTesting = RCTRunningInTestEnvironment() ? true : false, - .reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder( - {.minor = [versions[@"minor"] doubleValue], - .major = [versions[@"major"] doubleValue], - .patch = [versions[@"patch"] doubleValue], - .prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]] ? nullptr : versions[@"prerelease"]}), + UIDevice *device = [UIDevice currentDevice]; + bool isForceTouchAvailable = [RCTTraitCollectionProxy sharedInstance].currentTraitCollection.forceTouchCapability == + UIForceTouchCapabilityAvailable; + auto versions = RCTGetReactNativeVersion(); + return typedConstants({ + .forceTouchAvailable = isForceTouchAvailable, + .osVersion = [device systemVersion], + .systemName = [device systemName], + .interfaceIdiom = interfaceIdiom([device userInterfaceIdiom]), + .isTesting = RCTRunningInTestEnvironment() ? true : false, + .reactNativeVersion = JS::NativePlatformConstantsIOS::ConstantsReactNativeVersion::Builder( + {.minor = [versions[@"minor"] doubleValue], + .major = [versions[@"major"] doubleValue], + .patch = [versions[@"patch"] doubleValue], + .prerelease = [versions[@"prerelease"] isKindOfClass:[NSNull class]] ? nullptr : versions[@"prerelease"]}), #if TARGET_OS_MACCATALYST - .isMacCatalyst = true, + .isMacCatalyst = true, #else - .isMacCatalyst = false, + .isMacCatalyst = false, #endif - }); }); - - return constants; } - (std::shared_ptr)getTurboModule:(const ObjCTurboModule::InitParams &)params diff --git a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm index 5142307e8b94fa..26d75e51aa02a6 100644 --- a/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm +++ b/packages/react-native/ReactCommon/react/runtime/platform/ios/ReactCommon/RCTHost.mm @@ -12,6 +12,7 @@ #import #import #import +#import #import #import #import @@ -248,6 +249,8 @@ - (RCTFabricSurface *)createSurfaceWithModuleName:(NSString *)moduleName mode:(DisplayMode)displayMode initialProperties:(NSDictionary *)properties { + RCTInitializeUIKitProxies(); + RCTFabricSurface *surface = [[RCTFabricSurface alloc] initWithSurfacePresenter:self.surfacePresenter moduleName:moduleName initialProperties:properties];