From 5717eea79342daef6bf1c0b69720743548dbece0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oskar=20Kwas=CC=81niewski?= Date: Fri, 5 Jan 2024 07:04:55 -0800 Subject: [PATCH] feat: refactor `RCTKeyWindow` to be more resilient and work in multi-window apps (#42036) Summary: This PRs refactors `RCTKeyWindow()` to be more resilient and work in multi-window apps. After my recent PR got merged (https://github.com/facebook/react-native/issues/41935) it significantly reduced the number of calls to `RCTKeyWindow()` and now it's called only when necessary. So this PR makes this function a bit more resource intensive but it guarantees that we will find current scene's key window. This would also fix some brownfield scenarios where React Native is working in multi-window mode and in the future allow us to more easily adopt `UIWindowSceneDelegate` bypass-github-export-checks [IOS] [CHANGED] - refactor `RCTKeyWindow` to be more resilient and work in multi-window apps Pull Request resolved: https://github.com/facebook/react-native/pull/42036 Test Plan: Checkout RNTester example for Alerts and LoadingView. https://github.com/facebook/react-native/assets/52801365/8cf4d698-db6d-4a12-8d8d-7a5acf34858b Reviewed By: huntie Differential Revision: D52431720 Pulled By: cipolleschi fbshipit-source-id: 0d6ef1d46b2428c30c9f64dae66b95dbc69f0a3b --- packages/react-native/React/Base/RCTUtils.m | 16 +++++++++---- .../React/CoreModules/RCTAlertController.mm | 24 +------------------ .../React/CoreModules/RCTDevLoadingView.mm | 10 ++++---- 3 files changed, 18 insertions(+), 32 deletions(-) diff --git a/packages/react-native/React/Base/RCTUtils.m b/packages/react-native/React/Base/RCTUtils.m index 96bff171c827fb..648bceacfd841b 100644 --- a/packages/react-native/React/Base/RCTUtils.m +++ b/packages/react-native/React/Base/RCTUtils.m @@ -600,12 +600,20 @@ BOOL RCTRunningInAppExtension(void) return nil; } - // TODO: replace with a more robust solution - for (UIWindow *window in RCTSharedApplication().windows) { - if (window.keyWindow) { - return window; + for (UIScene *scene in RCTSharedApplication().connectedScenes) { + if (scene.activationState != UISceneActivationStateForegroundActive || + ![scene isKindOfClass:[UIWindowScene class]]) { + continue; + } + UIWindowScene *windowScene = (UIWindowScene *)scene; + + for (UIWindow *window in windowScene.windows) { + if (window.isKeyWindow) { + return window; + } } } + return nil; #else // [macOS return [NSApp keyWindow]; diff --git a/packages/react-native/React/CoreModules/RCTAlertController.mm b/packages/react-native/React/CoreModules/RCTAlertController.mm index 698d7d1ae7856e..7999a937e697bd 100644 --- a/packages/react-native/React/CoreModules/RCTAlertController.mm +++ b/packages/react-native/React/CoreModules/RCTAlertController.mm @@ -23,17 +23,7 @@ @implementation RCTAlertController - (UIWindow *)alertWindow { if (_alertWindow == nil) { - _alertWindow = [self getUIWindowFromScene]; - - if (_alertWindow == nil) { - UIWindow *keyWindow = RCTSharedApplication().keyWindow; - if (keyWindow) { - _alertWindow = [[UIWindow alloc] initWithFrame:keyWindow.bounds]; - } else { - // keyWindow is nil, so we cannot create and initialize _alertWindow - NSLog(@"Unable to create alert window: keyWindow is nil"); - } - } + _alertWindow = [[UIWindow alloc] initWithWindowScene:RCTKeyWindow().windowScene]; if (_alertWindow) { _alertWindow.rootViewController = [UIViewController new]; @@ -67,18 +57,6 @@ - (void)hide _alertWindow = nil; } - -- (UIWindow *)getUIWindowFromScene -{ - for (UIScene *scene in RCTSharedApplication().connectedScenes) { - if (scene.activationState == UISceneActivationStateForegroundActive && - [scene isKindOfClass:[UIWindowScene class]]) { - return [[UIWindow alloc] initWithWindowScene:(UIWindowScene *)scene]; - } - } - - return nil; -} #endif // [macOS] @end diff --git a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm index d201b5f532889f..5895b756cea3ce 100644 --- a/packages/react-native/React/CoreModules/RCTDevLoadingView.mm +++ b/packages/react-native/React/CoreModules/RCTDevLoadingView.mm @@ -120,12 +120,14 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo dispatch_async(dispatch_get_main_queue(), ^{ self->_showDate = [NSDate date]; + if (!self->_window && !RCTRunningInTestEnvironment()) { #if !TARGET_OS_OSX // [macOS] - UIWindow *window = RCTSharedApplication().keyWindow; + UIWindow *window = RCTKeyWindow(); CGFloat windowWidth = window.bounds.size.width; - self->_window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, windowWidth, window.safeAreaInsets.top + 10)]; + self->_window = [[UIWindow alloc] initWithWindowScene:window.windowScene]; + self->_window.frame = CGRectMake(0, 0, windowWidth, window.safeAreaInsets.top + 10); self->_label = [[UILabel alloc] initWithFrame:CGRectMake(0, window.safeAreaInsets.top - 10, windowWidth, 20)]; [self->_window addSubview:self->_label]; @@ -162,12 +164,10 @@ - (void)showMessage:(NSString *)message color:(RCTUIColor *)color backgroundColo self->_window.backgroundColor = backgroundColor; self->_window.hidden = NO; - - UIWindowScene *scene = (UIWindowScene *)RCTSharedApplication().connectedScenes.anyObject; - self->_window.windowScene = scene; #else // [macOS self->_label.stringValue = message; self->_label.textColor = color; + self->_label.backgroundColor = backgroundColor; [self->_window orderFront:nil]; #endif // macOS]