Commit 9a4fbaa
feat(iOS, Tabs): Add proper support for
## Description
Adds first-class support for UIKit's `moreNavigationController` — the
overflow tab that UIKit automatically creates when a
`UITabBarController` has more than 5 tabs. Previously the library had no
awareness of this controller, which led to crashes and broken state when
the user tapped the "More" tab or when JS attempted to navigate to it.
This PR follows the RFC-1028 state model introduced in #3781, extending
it to cover the `moreNavigationController` case.
Important thing to note from this PR is that it introduces a special
`rnscreens_moreNavigationController` *screenKey*. It is a reserved key,
that can not be used by the user.
## Changes
### Native (iOS)
- Introduced `kMoreNavigationControllerScreenKey`
(`"moreNavigationController"`) as the reserved key identifying the more
navigation controller across native and JS state.
- Updated `updateSelectedViewController` to detect when the pending
operation targets `moreNavigationController` and handle it separately
from regular tabs. If the controller is not present in the tab bar at
the time of the request (e.g. iPad sidebar/top bar mode), the operation
is silently dropped — a rejection event will be added in a follow-up.
- Fixed a crash in `tabBarController:shouldSelectViewController:` and
`tabBarController:didSelectViewController:` — previously these methods
unsafely cast `UINavigationController` (the more controller) to
`RNSTabsScreenViewController`.
- Removed the buggy `shouldAllowMoreControllerSelection` method, which
always returned `YES` due to a logic error (`!expr ?: YES`).
- Updated `updateSelectedViewControllerTo:` to accept an explicit
`screenKey` parameter instead of deriving it from the view controller,
eliminating a potential nil-key crash.
- When JS programmatically navigates to `moreNavigationController`, the
controller's stack is popped to root (animated only if the more tab is
already selected), so the user always sees the full list of overflow
tabs rather than whatever was previously on the navigation stack.
User-driven taps leave the stack untouched.
- `RNSTabBarController` now conforms to `UINavigationControllerDelegate`
and sets itself as delegate of `moreNavigationController` when it
becomes active. This allows tracking push/pop navigation within the more
list — when the user drills into a screen from the more list, the state
is updated accordingly.
- Aligned navigation bar suppression between user-driven and JS-driven
flows (the reason for suppression is still under investigation — likely
to hide the Edit button — tracked with a `TODO`).
- Added utility helpers guarded by
`RNS_MORE_NAVIGATION_CONTROLLER_AVAILABLE`:
`screenKeyForSelectedViewController`, `canHaveMoreNavigationController`,
`isSelectedViewControllerTheMoreNavigationController`,
`isMoreNavigationControllerPresentInTabBar`,
`isMoreNavigationControllerTabBarItemSelected`,
`isMoreNavigationControllerRequestedByOperation:`,
`popToRootInMoreNavigationControllerIfNeededAnimated:`,
`setupMoreNavigationControllerDelegateIfNeeded`,
`disableNavigationBarInMoreNavigationController`.
### JS / TypeScript
- `TabsContainer`: Added a guard that throws if any route config uses
`"moreNavigationController"` as its name, as that key is now reserved.
- `reducer`: The `"not found"` error is bypassed for
`moreNavigationController` key, allowing it to flow through the state
machine as a valid route.
- `reducer`: Fixed provenance calculation for `change-tab` action: next
provenance is now `max(suggestedState.provenance,
confirmedState.provenance) + 1` instead of `confirmedState.provenance +
1`. This prevents a rejected update (e.g. more controller not in tab
bar) from producing an identical provenance on retry, which would
silently block re-navigation.
## Visual documentation
> [!note]
> When watching this video, please pay attention to the fact that
*programmatic* navigation to the `moreNavigationController` always
navigates to the ROOT (more selection list) of that view controller.
While user-driven navigation preserves what's already on the stack of
the `moreNavigationController`. This is intended. Programmer can always
navigate to any given screen by requesting it by the key.
https://github.com/user-attachments/assets/5bf979ba-92a1-4e18-af4c-5f2aeef809ad
## Test plan
- Added `TestTabsMoreNavigationController` scenario in
`apps/src/tests/single-feature-tests/tabs/`.
- The scenario renders more than 5 tabs to trigger the "More" tab and
covers JS-driven navigation to `moreNavigationController` as well as
user-driven selection.
## Checklist
- [x] Included code example that can be used to test this change.
- [ ] Updated / created local changelog entries in relevant test files.
- [ ] For visual changes, included screenshots / GIFs / recordings
documenting the change.
- [ ] For API changes, updated relevant public types.
- [ ] Ensured that CI passes
---------
Co-authored-by: Copilot <[email protected]>moreNavigationController on iOS (#3785)1 parent a68e005 commit 9a4fbaa
9 files changed
Lines changed: 378 additions & 57 deletions
File tree
- apps/src
- tests/single-feature-tests/tabs
- ios/tabs/host
- src/components/tabs
- host
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | | - | |
9 | | - | |
| 9 | + | |
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
109 | | - | |
110 | | - | |
| 109 | + | |
111 | 110 | | |
112 | 111 | | |
113 | 112 | | |
| |||
200 | 199 | | |
201 | 200 | | |
202 | 201 | | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
203 | 206 | | |
204 | 207 | | |
205 | 208 | | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
206 | 213 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
| |||
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
13 | 15 | | |
| |||
58 | 60 | | |
59 | 61 | | |
60 | 62 | | |
61 | | - | |
| 63 | + | |
62 | 64 | | |
63 | 65 | | |
64 | 66 | | |
| |||
71 | 73 | | |
72 | 74 | | |
73 | 75 | | |
74 | | - | |
| 76 | + | |
75 | 77 | | |
76 | 78 | | |
77 | 79 | | |
| |||
83 | 85 | | |
84 | 86 | | |
85 | 87 | | |
86 | | - | |
| 88 | + | |
87 | 89 | | |
88 | 90 | | |
89 | 91 | | |
| |||
102 | 104 | | |
103 | 105 | | |
104 | 106 | | |
105 | | - | |
106 | 107 | | |
107 | 108 | | |
108 | 109 | | |
| |||
206 | 207 | | |
207 | 208 | | |
208 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | 15 | | |
| |||
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| 24 | + | |
23 | 25 | | |
24 | 26 | | |
25 | 27 | | |
| |||
Lines changed: 86 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
0 commit comments