fix(ios): propagate safe area insets to child UIKit views inside PagerView#1085
Conversation
|
@IsaacIsrael |
…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>
cc96767 to
0d8982c
Compare
|
@troZee done :) |
| private func propagateHostingSafeArea() { | ||
| var current: UIView? = view.superview | ||
| while let v = current { | ||
| if String(describing: type(of: v)).contains("_UIHostingView") { |
There was a problem hiding this comment.
Could you add an else statement and log this error if _UIHostingView is not found?
- Removed the private _UIHostingView class-name lookup.
- Added viewSafeAreaInsetsDidChange() so updates happen when UIKit safe-area values change.
- propagateSafeArea() now uses the nearest UIKit superview with non-zero safeAreaInsets, falling back to
view.window?.safeAreaInsets.
|
@IsaacIsrael In my PR, I applied the safer safe-area propagation in ios/Extensions.swift. What changed:
|
|
@wonhyunkwon Could you test this PR? |
|
@troZee How I applied it Environment
Scenario
Also checked our other PagerView usages (comment bottom sheet) and saw no safe-area regressions from the .ignoresSafeArea() + inset re-injection approach. As for #1097, I'll leave it open for now and defer to the maintainers on whether to close it in favor of this PR — feel free to close it if this one moves forward. |
|
Sorry @troZee , I missed your message and thanks @wonhyunkwon for testing :) |
|
@IsaacIsrael could you test the newest version of it works for you? 🙏 |
Fixes #1090 #1096
Problem
When using
react-native-pager-viewinside a screen with iOS safe area (e.g. behind a translucent tab bar on iOS 26 liquid glass), child UIKit views — includingUIScrollView/FlashList— receivesafeAreaInsets = .zero. This causescontentInsetAdjustmentBehaviorto have no effect: scroll views cannot automatically adjust their content insets to account for overlapping system chrome.Root cause
The current implementation uses
UIViewRepresentableto embed React Native views inside SwiftUI pages. TheUIHostingControlleris initialized withignoreSafeArea: true, which dynamically subclasses the hosting view to overridesafeAreaInsets→.zero. Even withoutignoreSafeArea, SwiftUI's layout system does not propagate safe area insets to child UIKit views embedded viaUIViewRepresentable.Debug evidence (from native logging added during investigation):
Solution
Three targeted changes:
Extensions.swift— ReplaceUIViewRepresentablewithUIViewControllerRepresentable. The newPageChildViewControllertraverses up to_UIHostingView, reads its realsafeAreaInsets, and re-injects them viaadditionalSafeAreaInsets. This restores proper safe area propagation to all descendant UIKit views.PagerView.swift— Add.ignoresSafeArea()to the SwiftUITabViewbody so page content extends visually behind system chrome (translucent tab bars, etc.), enabling the "scroll behind glass" effect.PagerViewProvider.swift— RemoveignoreSafeArea: truefromUIHostingControllerinit so the hosting view preserves real device insets forPageChildViewControllerto read.How it works
The guard with 0.5pt threshold prevents infinite layout loops when
viewDidLayoutSubviewssetsadditionalSafeAreaInsets.Test plan
contentInsetAdjustmentBehavior='scrollableAxes'works on scroll views inside PagerView pages (last item should stop above the tab bar when scrolled to bottom)Made with Cursor