fix(iOS, Tabs): fix moreNavigationController navigation bar visible on nav#3993
fix(iOS, Tabs): fix moreNavigationController navigation bar visible on nav#3993
Conversation
after first nav. We had an ugly error, where the navigation bar of more navigation controller would be visible & animated away from the user interface after the user had navigated for the first time to a screen that's hosted inside the more navigation controller. To mitigate that I've updated the `updateSelectedViewControllerInner` method. It now checks, whether the `nextSelectedViewController` is hosted inside the *more navigation controller* and if so, it preemptively hides the navigation bar. To correctly detect "whether the `nextSelectedViewController` is hosted inside the *more navigation controller*" I've tightened the `canHaveMoreNavigationController` condition, by verifying whether we're in appropriate size class at the moment of check. It has not been described by linked issue software-mansion/react-native-screens-labs#1101, but we had very similar issue, where the navigation bar would animate away after being visible for a moment on transition to and from more navigation controller, that happens when the size class changes. I've patched it by overriding the `traitCollectionDidChange:` method and hiding the navigation bar from there at appropriate moment. It's important to note, that we perform our operation AFTER calling super. We give UIKit time to update the model, before we query `self.selectedViewController`. I'm not using `registerForTraitCollectionChange:withAction:` API in lieu of deprecated `traitCollectionDidChange:`, because it's available only since iOS 17, while we need to keep compat with iOS 15.
|
It's open question, whether in cases like that should we extend the scenario for the cc @LKuchno |
There was a problem hiding this comment.
Pull request overview
This PR addresses an iOS Tabs UI glitch where moreNavigationController’s navigation bar briefly appears (then hides) when navigating to tabs hosted under the “More” controller, including during horizontal size-class transitions.
Changes:
- Adds logic to proactively hide
moreNavigationController’s navigation bar when selecting a screen hosted under “More”. - Tightens the “More is available” check to require compact horizontal size class and introduces a helper to detect “More-hosted” view controllers.
- Hooks into
traitCollectionDidChange:to handle size-class transitions affecting the More controller.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
5 is the index. 6 is the count of required controllers.
`isViewControllerPresentedFromTheMoreNavigationController`
Address review feedback on the moreNavigationController fix: - Rename isViewControllerPresentedFromTheMoreNavigationController to isViewControllerHostedByMoreNavigationController. "Presented" in UIKit implies modal presentation; "hosted by" reflects the actual relationship. - Guard against NSNotFound before comparing the index. indexOfObject: returns NSUIntegerMax when the view controller is absent from self.viewControllers (e.g. moreNavigationController itself), which previously passed the >= check and could yield a false positive. - Extract kFirstIndexInMoreNavigationController = 4 instead of deriving it as kMinCountOfVCsForMoreVCPresence - 2. The two are independent UIKit facts (threshold for More to appear vs first index hosted in More); the old formula coupled them by coincidence. - Note in canHaveMoreNavigationController that the size class check is empirical, so future readers know it can be revisited.
The index-based check in isViewControllerHostedByMoreNavigationController: was unsound under tab reordering. UITabBarController lets users reorder tabs through the More list's Edit button; after a reorder, a VC originally placed at index < 4 in self.viewControllers may end up hosted by More (and vice versa). The index in self.viewControllers does not move when this happens, so the check returned the wrong answer for reordered configurations and would have reintroduced the navigation-bar flicker this PR is meant to fix. Replace the index comparison with a direct ground-truth check: if the VC's tabBarItem is not currently in self.tabBar.items, it is hosted by the More navigation controller. The NSNotFound guard is kept to explicitly reject arbitrary external VCs. The now-unused kFirstIndexInMoreNavigationController constant is removed.
| { | ||
| [super traitCollectionDidChange:previousTraitCollection]; | ||
|
|
||
| if (previousTraitCollection == nil || self.selectedViewController == nil) { |
There was a problem hiding this comment.
nit: you may consider combining all 4 conditions, I don't see any reason for splitting this, until you're planning to add some logic/logs before that early return disableNavigationBarInMoreNavigationController
There was a problem hiding this comment.
I see that, I think its just thing of aesthetics - I did that deliberately to separate the guard statements from actual logic. I'll leave it as is.
Description
Fixes an issue on iOS Tabs where the
moreNavigationController's navigation bar would be briefly visible and then animated away after the user navigated for the first time to a screen hosted inside the more navigation controller.Also patches a closely related issue (not covered by the linked issue) where the navigation bar would briefly appear and animate away during transitions to/from the more navigation controller triggered by a horizontal size class change.
Closes: https://github.com/software-mansion/react-native-screens-labs/issues/1101
Changes
RNSTabBarController.mm:updateSelectedViewControllerInner, preemptively hide the navigation bar when thenextSelectedViewControlleris hosted inside the more navigation controller.canHaveMoreNavigationControllerto also requirehorizontalSizeClass == UIUserInterfaceSizeClassCompact, so the check correctly reflects when the more nav. controller is actually present.isViewControllerPresentedFromTheMoreNavigationController:helper that combines the above with the tab bar items check and the index check (accounting for the slot replaced by the more nav. controller).traitCollectionDidChange:to handle size-class transitions (callsuperfirst, then queryself.selectedViewControllerso UIKit has updated its model). Used the deprecated API instead ofregisterForTraitCollectionChange:withAction:because the latter is iOS 17+ and we still support iOS 15.RCTAssertthat_pendingStateUpdatemust not be nil inupdateSelectedViewControllerInner.5intokMinCountOfVCsForMoreVCPresenceconstant with a link to the Apple docs.Visual documentation
1101-before.mov
1101-after.mov
Test plan
Manual repro on iOS (iPhone, compact horizontal size class):
FabricExampleon iOS.Additional case (size-class transitions):
Checklist