fix(iOS, Tabs): resolve content scroll view for tabBarMinimizeBehavior in real-world RN hierarchies#4153
Open
sgup wants to merge 1 commit into
Open
Conversation
…r in real-world RN hierarchies
On iOS 26, UIKit drives tabBarMinimizeBehavior (tab-bar minimize + inline
bottom accessory) from the selected tab view controller's bottom-edge
content scroll view. UIKit's automatic detection cannot find a UIScrollView
nested inside a typical React Native screen tree — styled wrapper views,
headers, virtualized lists (FlashList/LegendList), or a nested stack
navigator inside the tab — so minimize never engages. The
collapsable={false} workaround from software-mansion#3954 only addresses the trivial
single-wrapper case and cannot help with nested stacks or virtualized
lists.
Implement contentScrollViewForEdge: (UIKit's documented override point) on
RNSTabsScreenViewController and RNSScreen, covering whichever controller
UIKit queries: try UIKit's own resolution first, then fall back to a
breadth-first search of the attached content. Restricted to the bottom edge
so top-edge (navigation bar) resolution is unaffected.
The new RNSScrollViewFinder.findScrollViewBreadthFirstFrom: is a bounded BFS
that skips horizontal-only scrollers (carousels), so minimize tracks the
vertical list.
Fixes software-mansion#4145
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.
Note: The code / PR is made by Claude Fable, however I've manually tested and confirmed with my app on simulator and device.
Description
On iOS 26,
tabBarMinimizeBehavior: 'onScrollDown'never engages when a tab's content is a nested (native stack) navigator and/or the scrolling content is a virtualized list (FlashList, LegendList) or anyUIScrollViewbehind styled wrapper views — i.e. the common shape of a production app. The tab bar never minimizes and a bottom accessory never reaches its inline placement.UIKit resolves the scroll view that drives minimize by asking the selected tab's view controller for its bottom-edge content scroll view (
contentScrollView(for:)/ automatic detection). UIKit's automatic detection cannot find aUIScrollViewnested inside a React Native screen tree. Thecollapsable={false}workaround from #3954 only addresses the trivial single-styled-wrapper case — it cannot help when the first-descendant chain runs through a nested stack host or a virtualized list's wrappers.Closes #4145.
Changes
RNSScrollViewFinder: newfindScrollViewBreadthFirstFrom:— a bounded breadth-first search (2000-view visit cap) that skips horizontal-only scrollers (carousels), so behaviors driven by the result track the vertical list. The existing first-descendant-chain walk is exactly what fails on real screens.RNSTabsScreenViewController+RNSScreen: implementcontentScrollViewForEdge:(UIKit's documented override point), covering whichever controller UIKit queries (the tab root, or the visible nested-stack screen). UIKit's own resolution is tried first; the BFS is only a fallback, and only for the bottom edge so top-edge (navigation bar) resolution is unaffected.Open questions for maintainers:
RNSScreenoverride applies to all stack screens, not only tab-hosted ones. We saw no regressions (bottom edge only), but it could be gated to tab-hosted screens or behind a prop if preferred.RNSContentScrollViewProvidingdelegation so the BFS lives behind the provider protocol.Test plan
Tested in a production Expo SDK 56 app (RN 0.85, new arch) on a physical iOS 26 device, with these changes applied to 4.25.2 via package patching (this area is unchanged between 4.25.2 and
main):Reproduction shape:
Same class of issue reported against react-native-bottom-tabs (callstack/react-native-bottom-tabs#496), where swizzling
contentScrollView(for:)is the community fix; this PR uses the documented override point instead.Checklist