From b7761aff89bec93ae298880cda86d5f10da501dd Mon Sep 17 00:00:00 2001 From: Floco Date: Wed, 4 Nov 2026 04:15:13 +0000 Subject: [PATCH] fix(ios): skip provider inset reports while outside a window A SafeAreaProvider that is laid out but outside a window (for example a tab of a native tab container that was never selected) reports zero safeAreaInsets. A keyboard notification then sent that zero to JS, so the tab's first frame painted with top: 0 and jumped when the real inset arrived. Skip the measurement without a window, as RNCSafeAreaViewComponentView already does, and request a layout pass when the view enters a window so the first report is always sent. Fixes #806 Co-authored-by: Nick Lamont <39568874+nicklamont@users.noreply.github.com> --- .../RNCSafeAreaProviderComponentView.mm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ios/Fabric/RNCSafeAreaProviderComponentView.mm b/ios/Fabric/RNCSafeAreaProviderComponentView.mm index b17786d6..96d6fb42 100644 --- a/ios/Fabric/RNCSafeAreaProviderComponentView.mm +++ b/ios/Fabric/RNCSafeAreaProviderComponentView.mm @@ -53,6 +53,18 @@ - (void)willMoveToSuperview:(UIView *)newSuperView [self registerNotifications]; } } + +- (void)didMoveToWindow +{ + [super didMoveToWindow]; + + // Insets measured while outside a window are skipped, so measure again on entry. safeAreaInsetsDidChange only + // fires when the value changes, which it may not if the in-window insets equal the zero held outside the window. + // Ask for a layout pass instead of reading here: UIKit may not have updated safeAreaInsets yet. + if (self.window != nil) { + [self setNeedsLayout]; + } +} #endif - (void)registerNotifications @@ -88,6 +100,13 @@ - (void)invalidateSafeAreaInsetsMeasuringCorners:(BOOL)measureCorners if (self.superview == nil) { return; } +#if !TARGET_OS_OSX + // A view outside a window reports zero safeAreaInsets, e.g. a tab of a native tab container that has not been + // selected yet. Keyboard notifications would send that zero to JS, so wait until the view enters a window. + if (self.window == nil) { + return; + } +#endif // This gets called before the view size is set by react-native so // make sure to wait so we don't set wrong insets to JS. if (CGSizeEqualToSize(self.frame.size, CGSizeZero)) {