diff --git a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm index 0bb5a4d97816..b9659df680ab 100644 --- a/packages/react-native/React/CoreModules/RCTDeviceInfo.mm +++ b/packages/react-native/React/CoreModules/RCTDeviceInfo.mm @@ -110,6 +110,16 @@ - (void)initialize name:UIApplicationDidBecomeActiveNotification object:nil]; +#if TARGET_OS_OSX // [macOS + // The window observed in init is the one that was key when this module was created, which is + // often none: an app usually makes its window key after starting React Native. Observe every + // window's resize instead, so the dimensions follow the window whenever it is resized. + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(interfaceFrameDidChange) + name:NSWindowDidResizeNotification + object:nil]; +#endif // macOS] + #if TARGET_OS_IOS _currentInterfaceOrientation = RCTKeyWindow().windowScene.interfaceOrientation; @@ -188,6 +198,25 @@ static BOOL RCTIsIPhoneNotched() } +#if TARGET_OS_OSX // [macOS +// The window whose size is reported. While the app is in the background no window is key or +// main, though the app's window is still on screen: fall back to the frontmost visible window +// that is not a panel, rather than to the screen. +static NSWindow *RCTDimensionsWindow(void) +{ + NSWindow *window = RCTKeyWindow() ?: NSApp.mainWindow; + if (window != nil) { + return window; + } + for (NSWindow *candidate in NSApp.orderedWindows) { + if (candidate.isVisible && ![candidate isKindOfClass:[NSPanel class]]) { + return candidate; + } + } + return nil; +} +#endif // macOS] + static NSDictionary *RCTExportedDimensions(CGFloat fontScale) { RCTAssertMainQueue(); @@ -203,14 +232,15 @@ static BOOL RCTIsIPhoneNotched() #else // [macOS NSScreen *mainScreen = NSScreen.mainScreen; CGSize screenSize = mainScreen.frame.size; - NSWindow *mainWindow = RCTKeyWindow(); + NSWindow *mainWindow = RCTDimensionsWindow(); #endif // macOS] // We fallback to screen size if a key window is not found. #if !TARGET_OS_OSX // [macOS] CGSize windowSize = mainWindow ? mainWindow.bounds.size : screenSize; //[macOS] #else // [macOS - CGSize windowSize = mainWindow ? mainWindow.frame.size : screenSize; //[macOS] + // The content's size, which is what the root view gets: the frame includes the title bar. + CGSize windowSize = mainWindow ? [mainWindow contentRectForFrameRect:mainWindow.frame].size : screenSize; //[macOS] #endif // macOS] @@ -341,7 +371,13 @@ - (void)_interfaceFrameDidChange NSDictionary *nextInterfaceDimensions = [self _exportedDimensions]; // update and publish the even only when the app is in active state - if (!([nextInterfaceDimensions isEqual:_currentInterfaceDimensions]) && RCTIsAppActive()) { +#if !TARGET_OS_OSX // [macOS] + const BOOL shouldPublish = RCTIsAppActive(); +#else // [macOS + // A Mac app's window stays on screen in the background, and can be resized there. + const BOOL shouldPublish = YES; +#endif // macOS] + if (!([nextInterfaceDimensions isEqual:_currentInterfaceDimensions]) && shouldPublish) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [[_moduleRegistry moduleForName:"EventDispatcher"] sendDeviceEventWithName:@"didUpdateDimensions"