Skip to content

fix(ios): keep pages out of the safe area again - #1140

Open
R4M80MrX wants to merge 4 commits into
callstack:masterfrom
R4M80MrX:fix/ios-restore-ignore-safe-area
Open

fix(ios): keep pages out of the safe area again#1140
R4M80MrX wants to merge 4 commits into
callstack:masterfrom
R4M80MrX:fix/ios-restore-ignore-safe-area

Conversation

@R4M80MrX

@R4M80MrX R4M80MrX commented Sep 3, 2026

Copy link
Copy Markdown

Summary

#1085 dropped ignoreSafeArea: true from the hosting controller when it introduced propagateSafeArea(). Restoring it fixes pages being laid out inside the safe area on iOS, and does not undo what #1085 set out to fix.

The two mechanisms are independent:

With the flag gone, the convenience initialiser it used, UIHostingController(rootView:ignoreSafeArea:), and disableSafeArea() became dead code; nothing in the repo calls them on main today.

What it looks like

Measured on an iPhone 17 Pro Max simulator (iOS 26.5, 440×956pt, safe area top 62 / bottom 34), New Architecture, react-native-pager-view 9.0.4, React Native 0.86.3. The app places the pager above an 83pt React Native tab bar, so React Native lays the pager out at y 0..873.

I put coloured borders on the views and read their positions off a screenshot:

view React Native layout on screen, before on screen, after
PagerView itself 0 .. 873 0 .. 872.7 0 .. 872.7
its page container 0 .. 873 31.0 .. 810.7 0 .. 872.7

The pages lost ~93pt and sat 31pt low. Anything anchored to the bottom of a page — in this app a right-hand action rail and the author/caption row — was pushed off screen. onLayout inside a page still reported the full 873pt, so nothing on the JS side could compensate for it.

Relation to #1099

#1099 reports the same family of symptom on 8.0.4 and also points at #1085, but diagnoses a different mechanism: propagateSafeArea() sampling _UIHostingView<_ViewList_View>'s bottom inset mid-relayout, while it flickers 49 → 24.33, and pinning the transient value.

This change is not a fix for that sampling race, and I could not reproduce #1099's "corrects itself after the first scroll" behaviour — what I see is stable from the first render onwards. Zeroing the outer hosting view's insets may also remove an unstable source from the nearestNonZeroSafeAreaInsets() walk, but the view named in #1099 is an inner hosting view for the page list, which disableSafeArea() does not subclass. That part is worth a maintainer's eye.

Test Plan

What's required for testing (prerequisites)?

An iOS device or simulator with a home-indicator safe area, New Architecture, and a pager that is not itself inset — for example full-screen content behind a custom React Native tab bar.

What are the steps to reproduce (after prerequisites)?

  1. Render a <PagerView> filling a screen whose top and bottom reach into the safe area.
  2. Give a page content anchored to its bottom (position: 'absolute', bottom: 0).
  3. Before: the anchored content is off screen and the page sits low, while onLayout on the page reports the full height.
  4. After: the page matches React Native's layout exactly and the anchored content is visible.

Verified in the app this came from: the page container went from 31.0 .. 810.7 to 0 .. 872.7 with no other change. The diff is iOS-only, so Android is untouched.

Compatibility

OS Implemented
iOS
Android n/a

Checklist

  • I have tested this on a device and a simulator — simulator only (iPhone 17 Pro Max, iOS 26.5); not on physical hardware
  • I added the documentation in README.md — not applicable
  • I updated the typed files (TS and Flow) — not applicable

callstack#1085 dropped `ignoreSafeArea: true` from the hosting controller when it added
`propagateSafeArea()`. The two solve different problems: the flag keeps SwiftUI
from laying the pages out inside the safe area, while `propagateSafeArea()`
hands child UIKit views their insets back. With the flag gone, `PagerView`'s
`GeometryReader` is measured inside the safe area and every page is framed to
that measurement, so the pages shrink and shift while React Native's layout
still has them at full size.

Co-authored-by: Cursor <cursoragent@cursor.com>
@troZee

troZee commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Based on this comment, it is not fixed yet #1142 (comment)

@troZee

troZee commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Restoring ignoreSafeArea: true keeps GeometryReader from shrinking the
pages, but nearestNonZeroSafeAreaInsets() then stops at an inner SwiftUI
host or the window. Those sources have the home-indicator inset and miss
a UINavigationController search bar, which is why callstack#1142's bottom recovered
and the top did not. Read PagerViewProvider.safeAreaInsets instead — that
view is still under the screen view controller.

Co-authored-by: Cursor <cursoragent@cursor.com>
@R4M80MrX

R4M80MrX commented Sep 8, 2026

Copy link
Copy Markdown
Author

@troZee I used iliapnmrv's repro.

The first commit only restored ignoreSafeArea: true. That is still required so GeometryReader does not shrink the pages — the bug this PR originally opened for. It also explains why @iliapnmrv saw bottom fixed, top still wrong: after the hosting view is forced to .zero, nearestNonZeroSafeAreaInsets() keeps walking and stops at an inner SwiftUI host or the window. Those sources have the home-indicator bottom inset, but they do not include a UINavigationController search bar. That height lives on the RN screen view controller.

contentInsetAdjustmentBehavior="automatic" on a FlatList inside the pager also never sees the search bar another way: UIKit / react-native-screens attach the search controller to the first UIScrollView in the descendant chain, which is now TabView's UICollectionView, not the list.

This follow-up leaves ignoreSafeArea: true in place and changes propagateSafeArea() to inject the PagerView UIView's own safeAreaInsets. That view is still under the screen view controller, so its top inset includes a stacked headerSearchBarOptions bar, and a pager that does not overlap the unsafe region gets zero (the nested-card case in #1099).

@iliapnmrv Could you re-test this branch with contentInsetAdjustmentBehavior="automatic" uncommented and the useHeaderHeight padding removed? Please rebuild natively (pod install + a fresh iOS build) — a JS reload is not enough.

@iliapnmrv

Copy link
Copy Markdown

Hi! Unfortunately, the latest commit didn’t change anything for me. The bottom inset is fixed, but there is still an extra inset at the top

PageChildViewController is under the hosting controller, which is a
child of the RN screen, so it already inherits the nav + search-bar
top inset. additionalSafeAreaInsets is additive: copying the pager's
own insets (often top 0, because RN already laid the pager below the
header) left that inherited extra in place, which is why callstack#1142 still
showed a gap after the previous commit. Set additional to
target - inherited — negative is required — so the page matches the
pager's overlap with the screen safe area.
@R4M80MrX

R4M80MrX commented Sep 9, 2026

Copy link
Copy Markdown
Author

@iliapnmrv Thanks for re-testing with contentInsetAdjustmentBehavior="automatic" — that extra top inset is real, and the last commit could not have fixed it.

PageChildViewController is attached under the UIHostingController, which is a child of the RN screen. It already inherits the nav + stacked search-bar top inset from that view controller. additionalSafeAreaInsets is additive, so copying PagerViewProvider.safeAreaInsets did nothing useful on the top edge: React Native has often already laid the pager below the header, the pager's own safeAreaInsets.top is 0, and the inherited extra stays. Same visual as the first commit.

Pushed another commit that sets additional = target - inherited (negative on purpose) so the page's resulting insets match the pager's overlap with the screen safeAreaLayoutGuide. Bottom should stay as it is; the extra top should go away.

Please rebuild natively once more (pod install + a fresh iOS build). Sorry for the extra round trip.

@iliapnmrv

Copy link
Copy Markdown

Tested as well in my production app, it works, thank you!

@troZee

troZee commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

@iliapnmrv @R4M80MrX
Thank you so much for testing it. @R4M80MrX could you add this repro example under ghIssues https://github.com/callstack/react-native-pager-view/blob/master/example/src/App.tsx#L114 ? Once it is added, please add Maestro tests to provide that safe area works fine.

Once it is added, I will merge it

troZee asked for the repro under ghIssues plus a Maestro check
before merge. The example is iliapnmrv's native-stack search-bar
case, and the flow asserts the pager list reports safe-area: pass
from the first-row / bottom-marker window positions.
@R4M80MrX

R4M80MrX commented Sep 9, 2026

Copy link
Copy Markdown
Author

@troZee Added.

  • Example: Issue #1142 Search Bar Inset Repro under ghIssues in example/src/App.tsx, same layout as iliapnmrv's repro (native-stack stacked search bar, contentInsetAdjustmentBehavior="automatic", control list vs list in PagerView).
  • Maestro: .maestro/issues/issue_1142_search_bar_inset_repro.yaml. It opens the pager screen and waits for safe-area: pass, which the example derives from measureInWindow on the first row and the bottom marker (first row just below the search bar, marker on the page bottom). Then it swipes to the second page.

Please run bun run maestro:test:ios on a simulator with a home-indicator safe area (or bun run e2e:ios for a full rebuild). I do not have the example app installed here.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The iOS safe-area change is coherent with the stated root cause, and the added example + Maestro regression flow provides targeted verification coverage for the new behavior.

Pull request overview

This PR restores iOS page layout to fill the full React Native–allocated frame by reintroducing ignoreSafeArea: true on the SwiftUI hosting controller, while updating safe-area propagation logic so embedded UIKit views inside pages still receive correct insets (including native navigation/search bar cases). It also adds a concrete example + Maestro regression flow for the related search-bar inset scenario.

Changes:

  • Re-add ignoreSafeArea: true when creating the UIHostingController to prevent SwiftUI safe-area from shrinking the GeometryReader measurement used to size pages.
  • Update PageChildViewController.propagateSafeArea() to compute target insets based on pager overlap with the screen safe area and apply them via additionalSafeAreaInsets (including allowing negative adjustments).
  • Add an example repro screen and Maestro regression flow for the “native-stack stacked search bar + FlatList in PagerView” inset behavior.
File summaries
File Description
ios/PagerViewProvider.swift Restores ignoreSafeArea: true and triggers relayout on safe-area changes so pages can recompute injected insets.
ios/Extensions.swift Reworks safe-area propagation to derive correct target insets from pager/screen overlap and apply via additionalSafeAreaInsets.
example/src/gh-issues/Issue1142SearchBarInsetRepro.tsx Adds a self-contained repro comparing plain vs PagerView-wrapped FlatList inset behavior.
example/src/App.tsx Registers the new #1142 repro entry and adds two navigation routes for the control/repro screens.
.maestro/setup/issue_1142_search_bar_inset_repro_setup.yaml Adds a shared setup flow to navigate to the #1142 pager repro screen.
.maestro/issues/issue_1142_search_bar_inset_repro.yaml Adds an iOS-tagged regression flow asserting the repro reports safe-area: pass and paging still works.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants