Skip to content

Commit d50c1b5

Browse files
cbrevikmeta-codesync[bot]
authored andcommitted
Fix recycled ScrollView inheriting stale contentInset on iOS (#56832)
Summary: Fixes #55090 In Fabric, `prepareForRecycle` resets contentInset to zero when a `ScrollView` is recycled. But the subsequent frame reset dance (required for `contentInsetAdjustmentBehavior` see 27fe6f1) triggers `centerContentIfNeeded` with the previous component's stale content size, silently overwriting the reset. As a result, a `ScrollView` with `centerContent={true}` that is unmounted and replaced by another `ScrollView` leaves behind a stale contentInset that the new component inherits causing displaced content if the new `ScrollView` does not have `centerContent={true}`. Two fixes: 1. `RCTScrollViewComponentView` In updateProps, when `centerContent` transitions from true to false, explicitly reset `contentInset` to the prop value. `centerContentIfNeeded` will not self-correct once `centerContent` is disabled on the new component, so the reset must be forced. The existing `contentInset` prop check is also guarded with !centerContent to prevent it from overriding the inset calculated by centerContentIfNeeded when centering is active. 2. `RCTEnhancedScrollView` Override `setCenterContent:` and `setContentSize:` to trigger `centerContentIfNeeded`. This ensures centering is re-applied with the correct new content size when the new component's content arrives via `updateState:`, and that toggling `centerContent` takes effect immediately. ## Changelog: [IOS] [FIXED] - Fix recycled ScrollView inheriting stale contentInset from centerContent on Fabric Pull Request resolved: #56832 Test Plan: Using the RNTester-app, replace `CenterContentList` with the following inside `ScrollViewExample.js`: <details><summary>CenterContentList reproducer</summary> Doing two different ScrollViews to force an unmount of the one with centerContent so the other inherits its insets ```jsx function CenterContentList(): React.Node { const [centerContent, setCenterContent] = useState(true); if (centerContent) { return ( <ScrollView nestedScrollEnabled style={styles.scrollView} centerContent={true}> <Text>This should be in center.</Text> <Button label="Toggle centerContent" onPress={() => setCenterContent(!centerContent)} /> </ScrollView> ); } else { return ( <ScrollView nestedScrollEnabled style={styles.scrollView} centerContent={false}> <Text>This should not be in center.</Text> <Button label="Toggle centerContent" onPress={() => setCenterContent(!centerContent)} /> </ScrollView> ); } } ``` </details> ### Before the fix Note that for both `ScrollView`s the content is centered, even though one has `centerContent={false}`. https://github.com/user-attachments/assets/7d5916a2-dbe3-4955-ad7b-1e6596d90104 ### After the fix One is properly centered, while the other has its content at the top. https://github.com/user-attachments/assets/ae8a41eb-0084-40e7-98fd-88433956ea40 Reviewed By: cipolleschi Differential Revision: D105294296 Pulled By: javache fbshipit-source-id: 4dfd2ecddb2c381e204da3a9278e74829ea702b5
1 parent 3a3a648 commit d50c1b5

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTEnhancedScrollView.mm

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ - (void)setContentOffset:(CGPoint)contentOffset
102102
RCTSanitizeNaNValue(contentOffset.y, @"scrollView.contentOffset.y"));
103103
}
104104

105+
- (void)setCenterContent:(BOOL)centerContent
106+
{
107+
if (_centerContent != centerContent) {
108+
_centerContent = centerContent;
109+
[self centerContentIfNeeded];
110+
}
111+
}
112+
113+
- (void)setContentSize:(CGSize)contentSize
114+
{
115+
[super setContentSize:contentSize];
116+
[self centerContentIfNeeded];
117+
}
118+
105119
- (void)setFrame:(CGRect)frame
106120
{
107121
[super setFrame:frame];

packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,15 +396,24 @@ - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &
396396

397397
MAP_SCROLL_VIEW_PROP(zoomScale);
398398

399-
if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset) {
399+
// When disabling centerContent, reset inset to prop value
400+
// (enabling is handled automatically by the setCenterContent: setter)
401+
if (oldScrollViewProps.centerContent && !newScrollViewProps.centerContent) {
400402
_scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
401403
}
402404

403-
RCTEnhancedScrollView *scrollView = (RCTEnhancedScrollView *)_scrollView;
405+
// Only apply contentInset from props if centerContent is disabled
406+
// When centerContent is enabled, the inset is calculated by centerContentIfNeeded
407+
if (oldScrollViewProps.contentInset != newScrollViewProps.contentInset && !newScrollViewProps.centerContent) {
408+
_scrollView.contentInset = RCTUIEdgeInsetsFromEdgeInsets(newScrollViewProps.contentInset);
409+
}
404410
if (oldScrollViewProps.contentOffset != newScrollViewProps.contentOffset) {
405411
_scrollView.contentOffset = RCTCGPointFromPoint(newScrollViewProps.contentOffset);
406412
}
407413

414+
// RCTEnhancedScrollView specific props
415+
416+
auto *scrollView = (RCTEnhancedScrollView *)_scrollView;
408417
if (oldScrollViewProps.snapToAlignment != newScrollViewProps.snapToAlignment) {
409418
scrollView.snapToAlignment = RCTNSStringFromString(toString(newScrollViewProps.snapToAlignment));
410419
}

0 commit comments

Comments
 (0)