Skip to content

fix(iOS): add fall back to window.safeAreaInsets when no ancestor provider is found - #4440

Open
Loloekk wants to merge 3 commits into
mainfrom
@Loloekk/1661-sav-doesnt-work-on-ios
Open

fix(iOS): add fall back to window.safeAreaInsets when no ancestor provider is found #4440
Loloekk wants to merge 3 commits into
mainfrom
@Loloekk/1661-sav-doesnt-work-on-ios

Conversation

@Loloekk

@Loloekk Loloekk commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Description

On iOS, SafeAreaView did not apply insets unless it had an ancestor provider such as Screen, tabs, or a stack container. SafeAreaView from react-native-safe-area-context similarly requires a SafeAreaProvider.

Using the SafeAreaView itself as a fallback provider (as RNSAC does with return self) does not work with our implementation. We apply insets as margin, which changes the view’s frame. UIKit then recomputes safeAreaInsets for that smaller frame, which updates the margin again and causes an infinite layout loop.

When no ancestor provider is found, this change falls back to window.safeAreaInsets. That gives stable system chrome insets (status bar, home indicator, etc.) without the feedback loop. This matches the intended no-provider case: there is no navigation chrome to account for, so window-level system insets are the correct source.

Closes https://github.com/software-mansion/react-native-screens-labs/issues/1661

Changes

  • Updated ios/safe-area/RNSSafeAreaViewComponentView.mm to fall back to window insets when no ancestor provider is found, instead of skipping the update

Before & after - visual documentation

Before After
simulator_screenshot_B58A1CD0-07CC-4BF9-B77A-3368FE70E8CD simulator_screenshot_8E9A1FA1-625E-485C-95C5-653F60CAC2BC

Test plan

  • Minimal repro: Test4440SafeAreaView from react-native-screens/experimental with no provider screen; verify insets on iOS.
  • Checked TabsContainer inside SafeAreaView — works as expected (no overlaps or other layout issues).

Checklist

  • Included code example that can be used to test this change.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

@Loloekk
Loloekk requested a review from t0maboro August 3, 2026 06:35
@Loloekk Loloekk self-assigned this Aug 3, 2026
@Loloekk Loloekk added platform:ios Issue related to iOS part of the library type:bug Something isn't working area:sav Issue related to SafeAreaView component labels Aug 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes iOS SafeAreaView (experimental) so it still applies safe-area insets when no ancestor safe-area provider is present by falling back to window.safeAreaInsets, avoiding the infinite layout loop that would occur when using the view itself as a provider.

Changes:

  • iOS: Trigger inset state updates via safeAreaInsetsDidChange when no provider exists, and compute insets from window.safeAreaInsets as a stable fallback.
  • Apps: Add a minimal repro scenario (Test4440) demonstrating SafeAreaView behavior without a provider.
  • Apps: Register the new issue-test export in the issue-tests index.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
ios/safe-area/RNSSafeAreaViewComponentView.mm Adds window-based fallback insets and updates state when safe-area changes with no provider.
apps/src/tests/issue-tests/Test4440.tsx Adds a minimal test case for SafeAreaView without an ancestor provider.
apps/src/tests/issue-tests/index.ts Exposes Test4440 via the issue-tests entrypoint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Loloekk Loloekk added the action:backport-to-v4 Add this label to any issue or PR that should be backported to the v4 line of the library. label Aug 3, 2026

@t0maboro t0maboro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some comments, I'd put this PR on hold until @kkafar return from PTO

// SafeAreaView applies insets as margin (not padding), so backgroundColor
// does not paint into the system safe-area — the parent shows through there.
<SafeAreaView
style={{ flex: 1, backgroundColor: 'blue' }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we have our own color palette, which you can use import { Colors } from '@apps/shared/styling';

// does not paint into the system safe-area — the parent shows through there.
<SafeAreaView
style={{ flex: 1, backgroundColor: 'blue' }}
edges={{ top: true, bottom: true }}>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd extend this test a bit, allowing all 4 edges to be programmatically configurable using some Buttons and state, having an option to test more valid configurations in a single example.


- (UIEdgeInsets)fallbackSafeAreaInsets
{
return self.window.safeAreaInsets;

@t0maboro t0maboro Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a concern about this approach that window insets aren't respecting the view's position, while on native iOS own safeAreaInsets are calculated with respect to the view hierarchy. Reading the value from the window may incorrectly apply the margin in e.g. modals, which are positioned below the top inset.
I see the problem with using self as an insets fallback combined with applying margins instead of padding.

Right now I'm considering a different approach, which would be exposing an explicit SafeAreaProvider component to the public API that would implement RNSSafeAreaProviding with its own safeAreaInsets + change notifications, and log a warning when there's no provider at all. Anyway, I'd wait with the final decision for @kkafar, as he did much more research regarding SAV.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to cc @kligarski here.

I'm not certain about right direction here. Would it make sense to simply make a root view controller of each hierarchy a default provider in such cases? wdyt?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to simply make a root view controller of each hierarchy a default provider in such cases? wdyt?

I'm not sure what you mean here. We would need to make React's root view controller implement RNSSafeAreaProviding?

Regarding the issue highlighted by @t0maboro, I think I agree with the concern regarding the solution in this PR. I think we should consider either separate provider or changing how SAV work from margin to padding - I don't think we had any particular reason why we went with the margin approach but I might be forgetting something here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action:backport-to-v4 Add this label to any issue or PR that should be backported to the v4 line of the library. area:sav Issue related to SafeAreaView component platform:ios Issue related to iOS part of the library type:bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants