Skip to content

[iOS] Reset ScrollView frame when recycling - #59362

Merged
meta-codesync[bot] merged 1 commit into
react:mainfrom
kirillzyusko:codex/reset-scrollview-frame-on-recycle
Nov 4, 2026
Merged

meta-codesync[bot] merged 1 commit into
react:mainfrom
kirillzyusko:codex/reset-scrollview-frame-on-recycle

Conversation

@kirillzyusko

@kirillzyusko kirillzyusko commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary:

An interactive native-stack swipe-back with keyboard dismissal and native-driven Animated height updates can leave the internal UIScrollView with a negative frame origin. prepareForRecycle resets the outer component's frame, but retains the inner view's geometry. Reopening the screen reuses that offset view, shifting its content upward and leaving a gap at the bottom.

Reset _scrollView.frame to self.bounds at the end of prepareForRecycle.

Warning

To reproduce this problem I had to use new experimental shared Animated backend + drive height updates. And also use native-stack. I didn't find a way to reproduce it without react-native-screens/native-stack, but I strongly believe that root cause lives in react-native core.

Minimal runnable reproduction | App.js code | Reproduction changes

The repro uses React Native Animated, native-stack, screens and safe-area-context. It has no Reanimated, Worklets or keyboard-controller dependency (though original problem was discovered with react-native-keyboard-controller (#1200) /react-native-reanimated/react-native-worklets - in this PR I tried to isolate changes as close to bare RN as possible, but I still had to use react-native-screens for native navigation with gestures and all that stuff. Without navigation I didn't find a way to reproduce this problem - but repro confirms that it's not reanimated/keyboard-controlelr specific issue).

The public Experimental release preset enables the shared Animated backend and C++ Native Animated.

Related report: react-native-keyboard-controller#1200.

Changelog:

[IOS] [FIXED] - Reset the inner ScrollView frame when recycling to prevent a stale offset on reuse.

Test Plan:

Verified using iPhone 16 Pro Simulator (iOS 18.5), Xcode 27.0, React Native 0.87.1 / Fabric.

Reproduction

At the pinned reproduction commit:

yarn
bundle install
cd ios && bundle exec pod install && cd ..
yarn start
# In another terminal:
yarn ios --simulator "iPhone 16 Pro"

Enable the simulator's software keyboard. The interpolation constants target this device's 402 × 874 pt screen.

  1. Freshly launch the app.
  2. Press Open ScrollView.
  3. Focus Type a message and wait for the keyboard.
  4. Press Scroll to end.
  5. Swipe back from the left phone bezel, with the keyboard initially open.
  6. Press Open ScrollView again.

Important

The first opening maps native onScroll events to height using Animated.event with useNativeDriver: true. Keyboard dismissal triggers a second resize after 100 ms.

⚠️ The second opening uses a static height to expose the recycled geometry.

Then run yarn apply-fix, rebuild with yarn ios --simulator "iPhone 16 Pro", and repeat the same steps.

Results

After reopening Before After
Inner ScrollView frame.y −128.6667 pt 0 pt
Same native inner and outer views reused Yes Yes
React tag changes on reuse 218 -> 426 218 -> 426
Yellow gap below content Visible Absent

Screenshots — iPhone 16 Pro Simulator, iOS 18.5

Before After
Before — iPhone 16 Pro Simulator, iOS 18.5 After — iPhone 16 Pro Simulator, iOS 18.5

Videos — iPhone 16 Pro Simulator, iOS 18.5

Before After
before.mp4
after-english.mp4

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 22, 2026
@kirillzyusko
kirillzyusko marked this pull request as ready for review October 22, 2026 17:06
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Oct 22, 2026
@kirillzyusko
kirillzyusko force-pushed the codex/reset-scrollview-frame-on-recycle branch from a875177 to 2f4e0a0 Compare October 23, 2026 09:09
@github-actions

Copy link
Copy Markdown

Visual regression

No visual differences at 2f4e0a0d49f9.

android: no capture

@kirillzyusko

Copy link
Copy Markdown
Contributor Author

Hey @tomekzaw 👋

May I also kindly ask you to check this issue, please? 🙏

Initially I discovered it using reanimated/worklets/native-stack, but then managed to nail it down to plain native-stack + new shared backend on which you guys are working form SWM

So you may have more context on this? 🤔

@meta-codesync

meta-codesync Bot commented Nov 4, 2026

Copy link
Copy Markdown

@bartlomiejbloniarz has imported this pull request. If you are a Meta employee, you can view this in D129585510.

@javache javache left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review automatically exported from Phabricator review in Meta.

@meta-codesync
meta-codesync Bot merged commit f6c9a85 into react:main Nov 4, 2026
95 checks passed
@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @kirillzyusko in f6c9a85

When will my fix make it into a release? | How to file a pick request?

@react-native-bot react-native-bot added the Merged This PR has been merged. label Nov 4, 2026
@kirillzyusko

Copy link
Copy Markdown
Contributor Author

Awesome, thank you @javache and @bartlomiejbloniarz for your time and review ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants