software-mansion / software-mansion/react-native-screens
[iOS 26] tabBarMinimizeBehavior never engages when tab content is a nested stack / virtualize list
@kkafar is already working on this.
Since Jun 10, 2026.
- Dominant language
- TypeScript
- Stars
- 3.7k
- Forks
- 713
- Avg merge
- 2d 23h
- Merged PRs (30d)
- 71
Description
Description
On iOS 26, tabBarMinimizeBehavior: 'onScrollDown' never engages when the tab's content is a nested (native stack) navigator and/or the scrolling content is a virtualized list (FlashList, LegendList) or any scroll view behind styled wrapper views — i.e. the common shape of a production app. The tab bar never minimizes, and a bottom accessory never transitions to its inline placement.
#3954 covered a narrow variant of this (scroll view wrapped in a single styled View) and was closed with the collapsable={false} workaround. That workaround can't help here: with a nested stack the first-descendant chain from the TabsScreen runs through the stack host and screen views, and with virtualized lists + headers the UIScrollView is simply not on the first-subview chain at all — there is nothing the app author can collapsable={false} their way out of.
Mechanism
UIKit resolves the scroll view that drives the minimize behavior 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 a UIScrollView nested inside a React Native screen tree, so it tracks nothing and UITabBarController.tabBarMinimizeBehavior is inert.
Reproduction shape
NativeTabs (tabBarMinimizeBehavior: onScrollDown)
└─ Tab
└─ native stack navigator
└─ screen
└─ styled wrappers / headers
└─ FlashList / LegendList / ScrollView
RNS 4.25.2, RN 0.85, iOS 26, new arch. (Same report exists for react-native-bottom-tabs: callstack/react-native-bottom-tabs#496, where swizzling contentScrollView(for:) is the known fix.)
Proposed fix (running in production via a patch)
Implement contentScrollViewForEdge: — UIKit's documented override point for exactly this — on RNSTabsScreenViewController and RNSScreen, falling back to a breadth-first search when UIKit's own resolution returns nil. Restricted to the bottom edge so top-edge/navigation-bar behaviors are untouched:
- (UIScrollView *)contentScrollViewForEdge:(NSDirectionalRectEdge)edge
{
UIScrollView *scrollView = [super contentScrollViewForEdge:edge];
if (scrollView != nil) {
return scrollView;
}
if (edge == NSDirectionalRectEdgeBottom) {
return [RNSScrollViewFinder findScrollViewBreadthFirstFrom:self.view];
}
return nil;
}
plus a new finder on RNSScrollViewFinder (the existing first-descendant-chain walk is what fails on real screens):
// Bounded BFS; skips horizontal-only scrollers (carousels) so the tab bar
// tracks vertical scrolling. ~2000-view visit cap as a safety bound.
+ (nullable UIScrollView *)findScrollViewBreadthFirstFrom:(nullable UIView *)view;
With both overrides in place (covering whichever VC UIKit queries — tab root or the visible nested-stack screen), minimize + inline accessory work with nested stacks and LegendList/FlashList content, verified on a physical iOS 26 device.
Notes / open questions for maintainers:
- The
RNSScreenoverride applies to all stack screens, not only those inside tabs. We saw no regressions (bottom-edge only), but you may prefer gating it to tab-hosted screens or behind a prop. - An alternative shape would be extending the
RNSContentScrollViewProvidingdelegation so the BFS lives behind the existing provider protocol.
Happy to open a PR with this.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.