From 1c8df99db998c612b344cd120ed1de7ae236d8ff Mon Sep 17 00:00:00 2001 From: JP Date: Fri, 25 Aug 2023 17:27:53 -0700 Subject: [PATCH] iOS: trigger didUpdateDimensions event when resizing without changing traits (#37649) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: - when using the `useWindowDimensions()` hook in a component, it's possible for the hook to report incorrect sizes and not update as frequently as it should - this is most applicable to apps built for iPad and macOS - closes https://github.com/facebook/react-native/issues/36118 - either when resizing a React Native app to a different [Size Class](https://developer.apple.com/design/human-interface-guidelines/layout#Device-size-classes) or changing the Appearance, we dispatch an `RCTUserInterfaceStyleDidChangeNotification` notification - these are then handled in the `interfaceFrameDidChange` method of `RCTDeviceInfo` - this results in a `didUpdateDimensions` Device Event, which in turn updates the results of `useWindowDimensions()` - see [RCTDeviceInfo.mm#L217-L232](https://github.com/facebook/react-native/blob/v0.72.0-rc.4/packages/react-native/React/CoreModules/RCTDeviceInfo.mm#L217-L232) - and [Dimensions.js#L119-L124](https://github.com/facebook/react-native/blob/v0.72.0-rc.4/packages/react-native/Libraries/Utilities/Dimensions.js#L119-L124) 🐛 **However** 🐛 - if you are resizing without triggering a `UITraitCollection` change, the Dimensions reported by `useWindowDimensions()` can become stale, until you either:- - hit a certain width that is considered a different Size Class - change the Appearance - background then resume the app - make the app full-screen - added a new `RCTRootViewFrameDidChangeNotification` notification - the thinking here is to avoid additional overhead by re-using the same `RCTUserInterfaceStyleDidChangeNotification` - maybe it's overkill? - the new notifications are sent from an override of `setFrame` on `RCTRootView` - the new notifications call the same `interfaceFrameDidChange` method of `RCTDeviceInfo` - Dimensions are now reported correctly when resizing; even within the same Size Class [IOS] [FIXED] - Dimensions could be reported incorrectly when resizing iPad or macOS apps Pull Request resolved: https://github.com/facebook/react-native/pull/37649 Test Plan: **Reproduction: https://github.com/jpdriver/Dimensions** or to recreate it yourself:- - Generate a new project - Change App.tsx ``` import * as React from 'react'; import {View, Text, useWindowDimensions} from 'react-native'; export default function App() { const {width, height} = useWindowDimensions(); return ( Width: {width} Height: {height} ); } ``` - Open the iOS project in Xcode and enable iPad support - Enable the iPad app to be used in any orientation - Run the app - Enable Stage Manager - Drag one of the corners to resize the app Reviewed By: javache Differential Revision: D46335537 Pulled By: NickGerleman fbshipit-source-id: 1533f511cf3805fdc9629a2ee115cc00e204d82c --- .../Libraries/AppDelegate/RCTAppDelegate.h | 3 ++- .../Libraries/AppDelegate/RCTAppDelegate.mm | 10 ++++++++++ packages/react-native/React/Base/RCTConstants.h | 2 ++ packages/react-native/React/Base/RCTConstants.m | 2 ++ .../react-native/React/CoreModules/RCTDeviceInfo.mm | 5 +++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h index 1eff6941be56b8..b463f9d3b8e773 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h @@ -50,10 +50,11 @@ * - (id)getModuleInstanceFromClass:(Class)moduleClass */ #if !TARGET_OS_OSX // [macOS] -@interface RCTAppDelegate : UIResponder +@interface RCTAppDelegate : UIResponder #else // [macOS @interface RCTAppDelegate : NSResponder #endif // macOS] + /// The window object, used to render the UViewControllers @property (nonatomic, strong) RCTPlatformWindow *window; // [macOS] @property (nonatomic, strong) RCTBridge *bridge; diff --git a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm index e4c38b82a64887..923279c8ac6fe0 100644 --- a/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm +++ b/packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm @@ -89,6 +89,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification UIViewController *rootViewController = [self createRootViewController]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; + self.window.windowScene.delegate = self; [self.window makeKeyAndVisible]; return YES; @@ -173,6 +174,15 @@ - (BOOL)runtimeSchedulerEnabled return YES; } +#pragma mark - UISceneDelegate +- (void)windowScene:(UIWindowScene *)windowScene + didUpdateCoordinateSpace:(id)previousCoordinateSpace + interfaceOrientation:(UIInterfaceOrientation)previousInterfaceOrientation + traitCollection:(UITraitCollection *)previousTraitCollection API_AVAILABLE(ios(13.0)) +{ + [[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewFrameDidChangeNotification object:self]; +} + #pragma mark - RCTCxxBridgeDelegate - (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge { diff --git a/packages/react-native/React/Base/RCTConstants.h b/packages/react-native/React/Base/RCTConstants.h index 217b7aabc21a10..acdef2557dfe46 100644 --- a/packages/react-native/React/Base/RCTConstants.h +++ b/packages/react-native/React/Base/RCTConstants.h @@ -10,6 +10,8 @@ RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotification; RCT_EXTERN NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey; +RCT_EXTERN NSString *const RCTRootViewFrameDidChangeNotification; + /** * This notification fires when the bridge initializes. */ diff --git a/packages/react-native/React/Base/RCTConstants.m b/packages/react-native/React/Base/RCTConstants.m index 03e5ffe984c6dd..e7673378008395 100644 --- a/packages/react-native/React/Base/RCTConstants.m +++ b/packages/react-native/React/Base/RCTConstants.m @@ -10,6 +10,8 @@ NSString *const RCTUserInterfaceStyleDidChangeNotification = @"RCTUserInterfaceStyleDidChangeNotification"; NSString *const RCTUserInterfaceStyleDidChangeNotificationTraitCollectionKey = @"traitCollection"; +NSString *const RCTRootViewFrameDidChangeNotification = @"RCTRootViewFrameDidChangeNotification"; + NSString *const RCTJavaScriptDidFailToLoadNotification = @"RCTJavaScriptDidFailToLoadNotification"; NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification"; NSString *const RCTJavaScriptWillStartExecutingNotification = @"RCTJavaScriptWillStartExecutingNotification"; diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index dd8e14e74bd2a9..f301b8ed15c334 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -73,6 +73,11 @@ - (void)initialize selector:@selector(interfaceFrameDidChange) name:RCTUserInterfaceStyleDidChangeNotification object:nil]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(interfaceFrameDidChange) + name:RCTRootViewFrameDidChangeNotification + object:nil]; #endif // [macOS] }