Skip to content

Commit

Permalink
iOS: trigger didUpdateDimensions event when resizing without changing…
Browse files Browse the repository at this point in the history
… traits (facebook#37649)

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 facebook#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

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[IOS] [FIXED] - Dimensions could be reported incorrectly when resizing iPad or macOS apps

Pull Request resolved: facebook#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 (
    <View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
      <Text>Width: {width}</Text>
      <Text>Height: {height}</Text>
    </View>
  );
}
```
- 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
  • Loading branch information
jpdriver authored and Saadnajmi committed Jan 29, 2024
1 parent 0e0d2b8 commit 1c8df99
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/react-native/Libraries/AppDelegate/RCTAppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@
* - (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
*/
#if !TARGET_OS_OSX // [macOS]
@interface RCTAppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
@interface RCTAppDelegate : UIResponder <UIApplicationDelegate, UISceneDelegate, RCTBridgeDelegate>
#else // [macOS
@interface RCTAppDelegate : NSResponder <NSApplicationDelegate, RCTBridgeDelegate>
#endif // macOS]

/// The window object, used to render the UViewControllers
@property (nonatomic, strong) RCTPlatformWindow *window; // [macOS]
@property (nonatomic, strong) RCTBridge *bridge;
Expand Down
10 changes: 10 additions & 0 deletions packages/react-native/Libraries/AppDelegate/RCTAppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -173,6 +174,15 @@ - (BOOL)runtimeSchedulerEnabled
return YES;
}

#pragma mark - UISceneDelegate
- (void)windowScene:(UIWindowScene *)windowScene
didUpdateCoordinateSpace:(id<UICoordinateSpace>)previousCoordinateSpace
interfaceOrientation:(UIInterfaceOrientation)previousInterfaceOrientation
traitCollection:(UITraitCollection *)previousTraitCollection API_AVAILABLE(ios(13.0))
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewFrameDidChangeNotification object:self];
}

#pragma mark - RCTCxxBridgeDelegate
- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/React/Base/RCTConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/React/Base/RCTConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native/React/CoreModules/RCTDeviceInfo.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}

Expand Down

0 comments on commit 1c8df99

Please sign in to comment.