Skip to content

[iOS] contentInset and contentOffset on recycled ScrollView is not reset on reuse #55090

@cbrevik

Description

@cbrevik

Description

It does not seem like contentInset or contentOffset is reset when prepareForRecyle is run on ScrollViews on iOS for New Architecture.

This is most noticeable when using centerContent={true} on ScrollViews with some height and not much content. If you unmount such a ScrollView, and mount another with small height (like a horizontal ScrollView in my example here), you will notice the content in the horizontal one is displaced quite a bit.

This is because the native RCTEnhancedScrollView is recycled between those mounts, and the inset and offset is re-used (see logs and video). This is not a problem on the old architecture.

If I apply a patch to actually recalculate the content inset on props change it fixes the issue: cbrevik@23d270c

Admittedly it does introduce some more coupling between RCTEnhancedScrollView and RCTScrollViewComponentView which might not be the best solution. And there are maybe more scenarios than what I've tested this will affect.

Steps to reproduce

  1. Use the RNTester-app with this reproducer
  2. Click Playground tab, and see that low-height horizontal ScrollView is at the top (as it should be).
  3. Click Vertical ScrollView With Centered Content-button, and notice that the content of vertical scroll view is at top (and not centered), which is a bug.
  4. Click Low Height Horizontal Scroll-button, and notice that now the low-height horizontal ScrollView is further down on the screen. Also a bug.

React Native Version

0.83.1

Affected Platforms

Runtime - iOS

Areas

Fabric - The New Renderer

Output of npx @react-native-community/cli info

System:
  OS: macOS 15.6.1
  CPU: (10) arm64 Apple M1 Pro
  Memory: 1.17 GB / 32.00 GB
  Shell:
    version: 4.0.2
    path: /opt/homebrew/bin/fish
Binaries:
  Node:
    version: 22.17.1
    path: /usr/local/bin/node
  Yarn:
    version: 1.22.22
    path: /opt/homebrew/bin/yarn
  npm:
    version: 10.9.2
    path: /usr/local/bin/npm
  Watchman:
    version: 2025.08.11.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.15.2
    path: /Users/cb/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 25.1
      - iOS 26.1
      - macOS 26.1
      - tvOS 26.1
      - visionOS 26.1
      - watchOS 26.1
  Android SDK:
    API Levels:
      - "31"
      - "34"
      - "35"
      - "36"
    Build Tools:
      - 34.0.0
      - 35.0.0
      - 36.0.0
    System Images:
      - android-35 | Google APIs ARM 64 v8a
      - android-35 | Google Play ARM 64 v8a
      - android-35 | Pre-Release 16 KB Page Size Google Play ARM 64 v8a
      - android-36 | Google APIs ARM 64 v8a
      - android-36 | Google Play ARM 64 v8a
      - android-36 | Pre-Release 16 KB Page Size Google Play ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2025.1 AI-251.26094.121.2512.13930704
  Xcode:
    version: 26.1.1/17B100
    path: /usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.16
    path: /usr/bin/javac
  Ruby:
    version: 3.2.0
    path: /Users/cb/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: false
  newArchEnabled: false
iOS:
  hermesEnabled: true
  newArchEnabled: true

Stacktrace or Logs

If I capture view hierarchy in Xcode, at first the horizontal ScrollView has this state:

<RCTEnhancedScrollView: 0x10cdc0e00; baseClass = UIScrollView; frame = (0 0; 382 661.333); clipsToBounds = YES; 
autoresize = W+H; gestureRecognizers = <NSArray: 0x600000d95e90>; layer = <CALayer: 0x600000d96220>; 
contentOffset: {0, 0}; contentSize: {200, 20}; adjustedContentInset: {0, 0, 0, 0} topEdge=<style=automatic backgroundCapture=color>>

Note the inset and offset: contentOffset: {0, 0}; contentSize: {200, 20}; adjustedContentInset: {0, 0, 0, 0}

If I navigate to the taller vertical ScrollView (and capture), it has this state:

<RCTEnhancedScrollView: 0x10cdc0e00; baseClass = UIScrollView; frame = (0 0; 382 661.333); clipsToBounds = YES; 
autoresize = W+H; gestureRecognizers = <NSArray: 0x600000d95e90>; layer = <CALayer: 0x600000d96220>; 
contentOffset: {0, 0}; contentSize: {382, 300}; adjustedContentInset: {0, 0, 0, 0} topEdge=<style=automatic backgroundCapture=color>>

Here the offset and inset is actually wrong, since it is supposed to center the content in the screen: contentOffset: {0, 0}; contentSize: {382, 300}; adjustedContentInset: {0, 0, 0, 0}

It has "inherited" those from the horizontal ScrollView. If I navigate back to the horizontal ScrollView:

<RCTEnhancedScrollView: 0x10cdc0e00; baseClass = UIScrollView; frame = (0 0; 382 661.333); clipsToBounds = YES; 
autoresize = W+H; gestureRecognizers = <NSArray: 0x600000d95e90>; layer = <CALayer: 0x600000d96220>; 
contentOffset: {0, -180.66666666666666}; contentSize: {200, 20}; adjustedContentInset: {180.66666666666663, 0, 180.66666666666663, 0} topEdge=<style=automatic backgroundCapture=color>>

Now these are wrong: contentOffset: {0, -180.66666666666666}; contentSize: {200, 20}; adjustedContentInset: {180.66666666666663, 0, 180.66666666666663, 0}

It has actually inherited the "center content" offset/inset from the vertical ScrollView.

On next navigate back to vertical, the content is properly centered for that ScrollView, but wrong going forward for the horizontal one.

Notice the pointer 0x10cdc0e00 is the same for all instances, so it actually recycled the same native ScrollView.

MANDATORY Reproducer

https://github.com/cbrevik/react-native/blob/0cd46dcec7521f4697c47b1a264b065cd4ca8c59/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js

Screenshots and Videos

Bugged Recycled ScrollView

  1. ✅ Horizontal ScrollView, content is at the top, correct offset/inset
  2. ❌ Switch to vertical ScrollView, content is not centered as it should be. Inherited zero inset/offset from horizontal ScrollView.
  3. ❌ Switch back to horizontal ScrollView, content is suddenly further down, inherited "centered" inset/offset from vertical ScrollView.
bugged_recycled_scrollview.mov

Fixed Recycled ScrollView (with my patch)

  1. ✅ Horizontal ScrollView, content is at the top, correct offset/inset
  2. ✅ Vertical ScrollView, content is centered, correct offset/inset
fixed-recycled-scrollview.mov

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions