Skip to content

fix(ios): propagate safe area insets to child UIKit views inside PagerView#1085

Open
IsaacIsrael wants to merge 1 commit into
callstack:masterfrom
IsaacIsrael:fix/ios-safe-area-propagation
Open

fix(ios): propagate safe area insets to child UIKit views inside PagerView#1085
IsaacIsrael wants to merge 1 commit into
callstack:masterfrom
IsaacIsrael:fix/ios-safe-area-propagation

Conversation

@IsaacIsrael

Copy link
Copy Markdown

Problem

When using react-native-pager-view inside a screen with iOS safe area (e.g. behind a translucent tab bar on iOS 26 liquid glass), child UIKit views — including UIScrollView / FlashList — receive safeAreaInsets = .zero. This causes contentInsetAdjustmentBehavior to have no effect: scroll views cannot automatically adjust their content insets to account for overlapping system chrome.

Root cause

The current implementation uses UIViewRepresentable to embed React Native views inside SwiftUI pages. The UIHostingController is initialized with ignoreSafeArea: true, which dynamically subclasses the hosting view to override safeAreaInsets.zero. Even without ignoreSafeArea, SwiftUI's layout system does not propagate safe area insets to child UIKit views embedded via UIViewRepresentable.

Debug evidence (from native logging added during investigation):

HostingController.view.safeAreaInsets: bottom: 83     ← correct
PagingCollectionView.safeAreaInsets:   bottom: 83     ← correct
Wrapper UIView.safeAreaInsets:         bottom: 0      ← LOST
RCTEnhancedScrollView.safeAreaInsets:  bottom: 0      ← zero → contentInsetAdjustmentBehavior has nothing to adjust

Solution

Three targeted changes:

  1. Extensions.swift — Replace UIViewRepresentable with UIViewControllerRepresentable. The new PageChildViewController traverses up to _UIHostingView, reads its real safeAreaInsets, and re-injects them via additionalSafeAreaInsets. This restores proper safe area propagation to all descendant UIKit views.

  2. PagerView.swift — Add .ignoresSafeArea() to the SwiftUI TabView body so page content extends visually behind system chrome (translucent tab bars, etc.), enabling the "scroll behind glass" effect.

  3. PagerViewProvider.swift — Remove ignoreSafeArea: true from UIHostingController init so the hosting view preserves real device insets for PageChildViewController to read.

How it works

HostingController.view (bottom: 83)  ← real insets preserved (no more ignoreSafeArea)
  → PagingCollectionView (bottom: 83)
    → [.ignoresSafeArea() — content extends behind tab bar]
      → PageChildViewController
        → additionalSafeAreaInsets = hosting.safeAreaInsets  ← RE-INJECTED
          → Child UIKit views (bottom: 83)  ← contentInsetAdjustmentBehavior works!

The guard with 0.5pt threshold prevents infinite layout loops when viewDidLayoutSubviews sets additionalSafeAreaInsets.

Test plan

  • Verify contentInsetAdjustmentBehavior='scrollableAxes' works on scroll views inside PagerView pages (last item should stop above the tab bar when scrolled to bottom)
  • Verify content scrolls behind translucent tab bar (liquid glass effect on iOS 26)
  • Verify no layout regression on non-liquid-glass screens (pre-iOS 26)
  • Verify horizontal paging still works correctly
  • Verify no infinite layout loops (check CPU usage)

Made with Cursor

…rView

SwiftUI's `.ignoresSafeArea()` modifier strips `safeAreaInsets` from all
child UIKit views embedded via `UIViewRepresentable`. This prevents
`contentInsetAdjustmentBehavior` from working on scroll views inside
pager pages — the scroll view sees `safeAreaInsets = .zero` even when
the hosting controller correctly reports the real device insets (e.g.
bottom: 83pt for a tab bar on iOS 26 liquid glass).

Fix: replace `UIViewRepresentable` with `UIViewControllerRepresentable`.
The new `PageChildViewController` traverses up to `_UIHostingView`,
reads its real `safeAreaInsets`, and re-injects them via
`additionalSafeAreaInsets`. This restores proper safe area propagation
to all descendant UIKit views (including `UIScrollView`), so
`contentInsetAdjustmentBehavior` works as expected.

Also adds `.ignoresSafeArea()` to the SwiftUI `TabView` body so page
content extends visually behind system chrome (tab bars, etc.), and
removes the `ignoreSafeArea: true` parameter from `UIHostingController`
init so the hosting view preserves real device insets for
`PageChildViewController` to read.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

1 participant