From 6fac40a3bb3823e5e6c6eea3d5fc8a272274880d Mon Sep 17 00:00:00 2001 From: zewillze Date: Sun, 18 Oct 2026 13:27:23 +0800 Subject: [PATCH] fix(ios): keep a hidden navigation bar hidden on iOS 15 On iOS 15 SwiftUI's UIHostingController installs UIKitNavigationBarBridge, which calls -[UINavigationController setNavigationBarHidden:NO] on the enclosing navigation controller whenever the hosting view appears. When that bar is deliberately hidden - e.g. the container react-native-screens creates for hasTwoStates tab/drawer navigators - the bridge re-shows an empty UINavigationBar, which then swallows touches meant for the JS header buttons underneath. Wrap the hosting controller so it restores the previous hidden state around viewWillAppear. The workaround lives in its own file and every entry point returns early on iOS 16+, so deleting the file and its single call site in PagerViewProvider.swift is all that is needed once iOS 15 support is dropped. --- ios/PagerViewIOS15NavigationBarCompat.swift | 65 +++++++++++++++++++++ ios/PagerViewProvider.swift | 4 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 ios/PagerViewIOS15NavigationBarCompat.swift diff --git a/ios/PagerViewIOS15NavigationBarCompat.swift b/ios/PagerViewIOS15NavigationBarCompat.swift new file mode 100644 index 00000000..9286daf4 --- /dev/null +++ b/ios/PagerViewIOS15NavigationBarCompat.swift @@ -0,0 +1,65 @@ +import SwiftUI +import UIKit + +/// Workaround for SwiftUI re-showing an intentionally hidden `UINavigationBar` on iOS 15. +/// +/// On iOS 15 `UIHostingController` installs SwiftUI's `UIKitNavigationBarBridge`, which calls +/// `-[UINavigationController setNavigationBarHidden:NO]` on the enclosing navigation controller +/// whenever the hosting view appears. When that bar is deliberately hidden — for example the +/// `RNSContainerNavigationController` that `react-native-screens` creates for `hasTwoStates` +/// tab/drawer containers — the bridge re-shows an empty `UINavigationBar` that then swallows +/// touches meant for the React Native views underneath (in this case the JS header buttons). +/// +/// On iOS 16 and later SwiftUI no longer re-shows the bar, so a plain `UIHostingController` is +/// returned and none of the code below runs. +/// +/// - Note: Delete this file together with the single `PagerViewIOS15NavigationBarCompat` call in +/// `PagerViewProvider.swift` once the minimum supported iOS version is 16. +enum PagerViewIOS15NavigationBarCompat { + /// Returns a hosting controller that keeps the enclosing navigation bar's hidden state across + /// the appearance callbacks on iOS 15, and a plain `UIHostingController` on iOS 16+. + static func makeHostingController( + rootView: Content, + ignoreSafeArea: Bool + ) -> UIHostingController { + if #available(iOS 16, *) { + return UIHostingController(rootView: rootView, ignoreSafeArea: ignoreSafeArea) + } + + let hostingController = PreservingNavigationBarHostingController(rootView: rootView) + if ignoreSafeArea { + hostingController.disableSafeArea() + } + + return hostingController + } +} + +/// `UIHostingController` that restores the enclosing navigation bar's hidden state around +/// `viewWillAppear`, see `PagerViewIOS15NavigationBarCompat`. +private final class PreservingNavigationBarHostingController: UIHostingController { + override func viewWillAppear(_ animated: Bool) { + let navigationController = enclosingNavigationController + let wasNavigationBarHidden = navigationController?.isNavigationBarHidden == true + super.viewWillAppear(animated) + if wasNavigationBarHidden { + navigationController?.setNavigationBarHidden(true, animated: false) + } + } + + private var enclosingNavigationController: UINavigationController? { + if let navigationController { + return navigationController + } + + var viewController: UIViewController? = self + while let current = viewController { + if let navigationController = current as? UINavigationController { + return navigationController + } + viewController = current.parent + } + + return nil + } +} diff --git a/ios/PagerViewProvider.swift b/ios/PagerViewProvider.swift index 82cf6f1e..a6019344 100644 --- a/ios/PagerViewProvider.swift +++ b/ios/PagerViewProvider.swift @@ -236,7 +236,9 @@ import UIKit // has them at full size. `PageChildViewController` reads *this* view's // insets (the RN screen's, including a native search bar) and re-injects // them; the two are independent. - let hostingController = UIHostingController( + // iOS 15 only, see PagerViewIOS15NavigationBarCompat.swift + // (delete together with the iOS 15 support). + let hostingController = PagerViewIOS15NavigationBarCompat.makeHostingController( rootView: PagerView(props: props, delegate: delegate), ignoreSafeArea: true )