Repository navigation
fix(iOS, Tabs): filter UIKit's didSelectTab: for programmatic selection while More is active - #4890
Merged
t0maboro merged 1 commit intoOct 13, 2026
Conversation
…on while More is active On the UITab path `applySelectedScreenController:` falls back to `setSelectedViewController:` while the More tab is active. UIKit sends `didSelectTab:` synchronously from inside that write and, since #4862, an unflagged `didSelectTab:` counts as a user selection, so a programmatic selection out of a More tab progressed the state twice and emitted a second, `user`-origin `onTabSelected`. Wrap the fallback in `_isApplyingProgrammaticTabSelection` like the other programmatic writes. Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
kkafar
approved these changes
Oct 13, 2026
kkafar
left a comment
Member
There was a problem hiding this comment.
LGTM.
Tested the runtime, seems to fix the regression.d
t0maboro
marked this pull request as ready for review
October 13, 2026 08:36
12 of 13 tasks
t0maboro
deleted the
@t0maboro/fix-more-programmatic-selection-duplicate
branch
October 13, 2026 08:40
t0maboro
added a commit
that referenced
this pull request
Oct 13, 2026
…on while More is active (#4890) (#4892) (generated by Claude) ## Description On the `UITab` API path (iOS 26.1+) a programmatic tab selection made while the More tab is active emits `onTabSelected` twice: a `user`-origin event first, then the `programmatic-js` one, and the navigation state provenance skips a value. It happens when the current tab overflows into More and was itself selected programmatically (e.g. `Select Sixth` from JS, then `Select Second`). Entering More through the More list and then selecting programmatically gives a single event. Found during the 4.30.0 release testing (#4884), regression from #4862; 4.29.0 and 4.28.0 emit one event. Root cause: `-[RNSTabBarController applySelectedScreenController:]` sets `_isApplyingProgrammaticTabSelection` only around `self.selectedTab = …`, the branch taken when the More item is NOT selected. While More is selected it falls back to `[self setSelectedViewController:]` without the flag. UIKit sends `tabBarController:didSelectTab:previousTab:` synchronously from inside that write, and since #4862 every unflagged `didSelectTab:` is treated as a user selection: `userDidSelectViewController:` progresses the state once more and emits a `user` event, after which the programmatic path emits its own event with the already-bumped provenance. Before #4862 the filter worked the other way round (flag set in `shouldSelectTab:`), so the programmatic `setSelectedViewController:` write was ignored. ## Changes - `RNSTabBarController -applySelectedScreenController:`: `_isApplyingProgrammaticTabSelection` now wraps the whole `UITab` branch, including the `setSelectedViewController:` fallback used while More is active. Same pattern as `installScreenControllers:` around `setTabs:` / `selectedTab`. ## Before & after - visual documentation | Before | After | | --- | --- | | <video src="https://github.com/user-attachments/assets/3336fc09-d892-4436-acd4-9b6d8fef900a" /> | <video src="https://github.com/user-attachments/assets/a3e3e738-3a59-4afb-bec5-d10ccdeac7b6" /> | ## Test plan - `tabs/test-tabs-more-navigation-controller-ios` (SFT), iPhone simulator, iOS 27.0 and 26.5: `Select Sixth`, then `Select Second`. Before: two `onTabSelected` toasts for `Second`; after: one, with the provenance advanced by exactly one (check the `onTabSelected` payload). Also More → Fifth → `Select First` (one event, unchanged) and user taps between More and regular tabs (unchanged). - `tabs-stack-v4/test-tabs-search-tab-activation-ios` on iOS 27.0, the #4862 scenario: reselecting the search tab through the collapsed tab bar still shows "Selected in JS: yes". - `tabs/test-tabs-navigation-state-search-tab` and `tabs/test-tabs-simple-nav` e2e: unchanged. Draft: the root cause comes from the triage done for #4884 (A/B against 4.28.0 / 4.29.0 / 4.30 with the same app and only the library sources swapped), the patch builds (Debug) but has not been run against the scenario yet. ## Checklist - [x] Included code example that can be used to test this change (`tabs/test-tabs-more-navigation-controller-ios`). - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [ ] For API changes, updated relevant public types. - [ ] Ensured that CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <[email protected]> (cherry picked from commit 4a87a71) Co-authored-by: Claude Fable 5.1 <[email protected]>
t0maboro
added a commit
that referenced
this pull request
Oct 13, 2026
…on while More is active (#4890) (#4892) (#4894) (generated by Claude) ## Description On the `UITab` API path (iOS 26.1+) a programmatic tab selection made while the More tab is active emits `onTabSelected` twice: a `user`-origin event first, then the `programmatic-js` one, and the navigation state provenance skips a value. It happens when the current tab overflows into More and was itself selected programmatically (e.g. `Select Sixth` from JS, then `Select Second`). Entering More through the More list and then selecting programmatically gives a single event. Found during the 4.30.0 release testing (#4884), regression from #4862; 4.29.0 and 4.28.0 emit one event. Root cause: `-[RNSTabBarController applySelectedScreenController:]` sets `_isApplyingProgrammaticTabSelection` only around `self.selectedTab = …`, the branch taken when the More item is NOT selected. While More is selected it falls back to `[self setSelectedViewController:]` without the flag. UIKit sends `tabBarController:didSelectTab:previousTab:` synchronously from inside that write, and since #4862 every unflagged `didSelectTab:` is treated as a user selection: `userDidSelectViewController:` progresses the state once more and emits a `user` event, after which the programmatic path emits its own event with the already-bumped provenance. Before #4862 the filter worked the other way round (flag set in `shouldSelectTab:`), so the programmatic `setSelectedViewController:` write was ignored. ## Changes - `RNSTabBarController -applySelectedScreenController:`: `_isApplyingProgrammaticTabSelection` now wraps the whole `UITab` branch, including the `setSelectedViewController:` fallback used while More is active. Same pattern as `installScreenControllers:` around `setTabs:` / `selectedTab`. ## Before & after - visual documentation | Before | After | | --- | --- | | <video src="https://github.com/user-attachments/assets/3336fc09-d892-4436-acd4-9b6d8fef900a" /> | <video src="https://github.com/user-attachments/assets/a3e3e738-3a59-4afb-bec5-d10ccdeac7b6" /> | ## Test plan - `tabs/test-tabs-more-navigation-controller-ios` (SFT), iPhone simulator, iOS 27.0 and 26.5: `Select Sixth`, then `Select Second`. Before: two `onTabSelected` toasts for `Second`; after: one, with the provenance advanced by exactly one (check the `onTabSelected` payload). Also More → Fifth → `Select First` (one event, unchanged) and user taps between More and regular tabs (unchanged). - `tabs-stack-v4/test-tabs-search-tab-activation-ios` on iOS 27.0, the #4862 scenario: reselecting the search tab through the collapsed tab bar still shows "Selected in JS: yes". - `tabs/test-tabs-navigation-state-search-tab` and `tabs/test-tabs-simple-nav` e2e: unchanged. Draft: the root cause comes from the triage done for #4884 (A/B against 4.28.0 / 4.29.0 / 4.30 with the same app and only the library sources swapped), the patch builds (Debug) but has not been run against the scenario yet. ## Checklist - [x] Included code example that can be used to test this change (`tabs/test-tabs-more-navigation-controller-ios`). - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [ ] For API changes, updated relevant public types. - [ ] Ensured that CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <[email protected]> (cherry picked from commit 4a87a71) Co-authored-by: Claude Fable 5.1 <[email protected]> (cherry picked from commit 30c0cfe) Co-authored-by: Claude Fable 5.1 <[email protected]>
Merged
structivist
pushed a commit
to sakiori/react-native-screens
that referenced
this pull request
Nov 12, 2026
…on while More is active (software-mansion#4890) (generated by Claude) ## Description On the `UITab` API path (iOS 26.1+) a programmatic tab selection made while the More tab is active emits `onTabSelected` twice: a `user`-origin event first, then the `programmatic-js` one, and the navigation state provenance skips a value. It happens when the current tab overflows into More and was itself selected programmatically (e.g. `Select Sixth` from JS, then `Select Second`). Entering More through the More list and then selecting programmatically gives a single event. Found during the 4.30.0 release testing (software-mansion#4884), regression from software-mansion#4862; 4.29.0 and 4.28.0 emit one event. Root cause: `-[RNSTabBarController applySelectedScreenController:]` sets `_isApplyingProgrammaticTabSelection` only around `self.selectedTab = …`, the branch taken when the More item is NOT selected. While More is selected it falls back to `[self setSelectedViewController:]` without the flag. UIKit sends `tabBarController:didSelectTab:previousTab:` synchronously from inside that write, and since software-mansion#4862 every unflagged `didSelectTab:` is treated as a user selection: `userDidSelectViewController:` progresses the state once more and emits a `user` event, after which the programmatic path emits its own event with the already-bumped provenance. Before software-mansion#4862 the filter worked the other way round (flag set in `shouldSelectTab:`), so the programmatic `setSelectedViewController:` write was ignored. ## Changes - `RNSTabBarController -applySelectedScreenController:`: `_isApplyingProgrammaticTabSelection` now wraps the whole `UITab` branch, including the `setSelectedViewController:` fallback used while More is active. Same pattern as `installScreenControllers:` around `setTabs:` / `selectedTab`. ## Before & after - visual documentation | Before | After | | --- | --- | | <video src="https://github.com/user-attachments/assets/3336fc09-d892-4436-acd4-9b6d8fef900a" /> | <video src="https://github.com/user-attachments/assets/a3e3e738-3a59-4afb-bec5-d10ccdeac7b6" /> | ## Test plan - `tabs/test-tabs-more-navigation-controller-ios` (SFT), iPhone simulator, iOS 27.0 and 26.5: `Select Sixth`, then `Select Second`. Before: two `onTabSelected` toasts for `Second`; after: one, with the provenance advanced by exactly one (check the `onTabSelected` payload). Also More → Fifth → `Select First` (one event, unchanged) and user taps between More and regular tabs (unchanged). - `tabs-stack-v4/test-tabs-search-tab-activation-ios` on iOS 27.0, the software-mansion#4862 scenario: reselecting the search tab through the collapsed tab bar still shows "Selected in JS: yes". - `tabs/test-tabs-navigation-state-search-tab` and `tabs/test-tabs-simple-nav` e2e: unchanged. Draft: the root cause comes from the triage done for software-mansion#4884 (A/B against 4.28.0 / 4.29.0 / 4.30 with the same app and only the library sources swapped), the patch builds (Debug) but has not been run against the scenario yet. ## Checklist - [x] Included code example that can be used to test this change (`tabs/test-tabs-more-navigation-controller-ios`). - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [ ] For API changes, updated relevant public types. - [ ] Ensured that CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5.1 <[email protected]> (cherry picked from commit 4a87a71)
structivist
pushed a commit
to sakiori/react-native-screens
that referenced
this pull request
Nov 12, 2026
Brings upstream's fix for user tab selections dropped by a mount transaction (software-mansion#4862, software-mansion#4890) and the opt-in horizontal sheet bars from software-mansion#2. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(generated by Claude)
Description
On the
UITabAPI path (iOS 26.1+) a programmatic tab selection made while the More tab is active emitsonTabSelectedtwice: auser-origin event first, then theprogrammatic-jsone, and the navigation state provenance skips a value. It happens when the current tab overflows into More and was itself selected programmatically (e.g.Select Sixthfrom JS, thenSelect Second). Entering More through the More list and then selecting programmatically gives a single event. Found during the 4.30.0 release testing (#4884), regression from #4862; 4.29.0 and 4.28.0 emit one event.Root cause:
-[RNSTabBarController applySelectedScreenController:]sets_isApplyingProgrammaticTabSelectiononly aroundself.selectedTab = …, the branch taken when the More item is NOT selected. While More is selected it falls back to[self setSelectedViewController:]without the flag. UIKit sendstabBarController:didSelectTab:previousTab:synchronously from inside that write, and since #4862 every unflaggeddidSelectTab:is treated as a user selection:userDidSelectViewController:progresses the state once more and emits auserevent, after which the programmatic path emits its own event with the already-bumped provenance. Before #4862 the filter worked the other way round (flag set inshouldSelectTab:), so the programmaticsetSelectedViewController:write was ignored.Changes
RNSTabBarController -applySelectedScreenController::_isApplyingProgrammaticTabSelectionnow wraps the wholeUITabbranch, including thesetSelectedViewController:fallback used while More is active. Same pattern asinstallScreenControllers:aroundsetTabs:/selectedTab.Before & after - visual documentation
before.mov
after.mov
Test plan
tabs/test-tabs-more-navigation-controller-ios(SFT), iPhone simulator, iOS 27.0 and 26.5:Select Sixth, thenSelect Second. Before: twoonTabSelectedtoasts forSecond; after: one, with the provenance advanced by exactly one (check theonTabSelectedpayload). Also More → Fifth →Select First(one event, unchanged) and user taps between More and regular tabs (unchanged).tabs-stack-v4/test-tabs-search-tab-activation-ioson iOS 27.0, the fix(iOS, Tabs): don't drop user selection of UISearchTab on mount transactions #4862 scenario: reselecting the search tab through the collapsed tab bar still shows "Selected in JS: yes".tabs/test-tabs-navigation-state-search-tabandtabs/test-tabs-simple-nave2e: unchanged.Draft: the root cause comes from the triage done for #4884 (A/B against 4.28.0 / 4.29.0 / 4.30 with the same app and only the library sources swapped), the patch builds (Debug) but has not been run against the scenario yet.
Checklist
tabs/test-tabs-more-navigation-controller-ios).🤖 Generated with Claude Code