From 24e087a43d306baecc43c5f7718897079f9d943e Mon Sep 17 00:00:00 2001 From: Bron Davies Date: Fri, 23 Oct 2026 08:46:29 -0600 Subject: [PATCH] fix(macos): keep Dimensions in step with the window Dimensions.get("window") and useWindowDimensions() could stay at their first value however the window was resized, and reported the frame rather than the content area: - the module observes the frame of the window that was key when it was created, which is usually none, so no resize was ever reported. It now observes NSWindowDidResizeNotification. - the size reported was the frame, title bar included. It is now the content rect, which is what the root view gets. - with no key window (the app in the background) it fell back to the screen size, or published nothing. It now falls back to the main window, then the frontmost visible non-panel window, and publishes a resize made in the background. --- .../React/CoreModules/RCTDeviceInfo.mm | 42 +++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) 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"