From cc967675737d76bca97105d28f154423e0ca1447 Mon Sep 17 00:00:00 2001 From: Isaac Israel Date: Sun, 21 Jun 2026 14:00:40 +0300 Subject: [PATCH] fix(ios): propagate safe area insets to child UIKit views inside PagerView MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- ios/Extensions.swift | 57 +++++++++++++++++++++++++++++++------ ios/PagerView.swift | 1 + ios/PagerViewProvider.swift | 3 +- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/ios/Extensions.swift b/ios/Extensions.swift index 22641a82..479b1015 100644 --- a/ios/Extensions.swift +++ b/ios/Extensions.swift @@ -4,19 +4,60 @@ import UIKit /** Helper used to render UIView inside of SwiftUI. + Uses UIViewControllerRepresentable to re-inject safe area insets + that SwiftUI's .ignoresSafeArea() strips from child UIKit views. */ -struct RepresentableView: UIViewRepresentable { +struct RepresentableView: UIViewControllerRepresentable { var view: UIView - // Adding a wrapper UIView to avoid SwiftUI directly managing React Native views. - // This fixes issues with incorrect layout rendering. - func makeUIView(context: Context) -> UIView { - let wrapper = UIView() - wrapper.addSubview(view) - return wrapper + func makeUIViewController(context: Context) -> PageChildViewController { + let viewController = PageChildViewController() + viewController.wrappedView = view + return viewController } - func updateUIView(_ uiView: UIView, context: Context) {} + func updateUIViewController(_ uiViewController: PageChildViewController, context: Context) {} +} + +class PageChildViewController: UIViewController { + var wrappedView: UIView? + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .clear + if let wrappedView { + view.addSubview(wrappedView) + } + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + propagateHostingSafeArea() + } + + /// Traverses up to the _UIHostingView and re-applies its bottom safe area + /// as additionalSafeAreaInsets on this child VC, since .ignoresSafeArea() + /// causes UIKit to report safeAreaInsets = .zero for embedded views. + private func propagateHostingSafeArea() { + var current: UIView? = view.superview + while let v = current { + if String(describing: type(of: v)).contains("_UIHostingView") { + let hosting = v.safeAreaInsets + let delta = UIEdgeInsets( + top: hosting.top, left: hosting.left, + bottom: hosting.bottom, right: hosting.right + ) + if abs(additionalSafeAreaInsets.top - delta.top) > 0.5 + || abs(additionalSafeAreaInsets.left - delta.left) > 0.5 + || abs(additionalSafeAreaInsets.bottom - delta.bottom) > 0.5 + || abs(additionalSafeAreaInsets.right - delta.right) > 0.5 { + additionalSafeAreaInsets = delta + } + return + } + current = v.superview + } + } } extension Collection { diff --git a/ios/PagerView.swift b/ios/PagerView.swift index 7b296a0e..867ea146 100644 --- a/ios/PagerView.swift +++ b/ios/PagerView.swift @@ -20,6 +20,7 @@ struct PagerView: View { .id(props.children.count) .background(.clear) .tabViewStyle(.page(indexDisplayMode: .never)) + .ignoresSafeArea() .environment(\.layoutDirection, props.layoutDirection.converted) .introspect(.tabView(style: .page), on: .iOS(.v14...)) { collectionView in self.collectionView = collectionView diff --git a/ios/PagerViewProvider.swift b/ios/PagerViewProvider.swift index 54fb5177..d4a086b5 100644 --- a/ios/PagerViewProvider.swift +++ b/ios/PagerViewProvider.swift @@ -107,8 +107,7 @@ import UIKit } self.hostingController = UIHostingController( - rootView: PagerView(props: props, delegate: delegate), - ignoreSafeArea: true + rootView: PagerView(props: props, delegate: delegate) ) if let hostingController, let parentViewController = reactViewController() { parentViewController.addChild(hostingController)