Skip to content

fix(ios): skip programmatic selectRow while user is scrolling (Fabric crash)#674

Open
raid5 wants to merge 1 commit into
react-native-picker:masterfrom
raid5:fix-ios-picker-scroll-race-crash
Open

fix(ios): skip programmatic selectRow while user is scrolling (Fabric crash)#674
raid5 wants to merge 1 commit into
react-native-picker:masterfrom
raid5:fix-ios-picker-scroll-race-crash

Conversation

@raid5

@raid5 raid5 commented Jul 17, 2026

Copy link
Copy Markdown

Summary

Fixes an intermittent native crash on iOS when a controlled Picker is scrolled quickly under the New Architecture (Fabric). The fix skips the programmatic re-selection of a row while the user is physically dragging/flinging the wheel.

Problem

With a controlled picker (selectedValue driven by React state), fast scrolling can crash the app with EXC_BAD_ACCESS inside UIKit's _endSuspendingUpdates. It reproduces reliably on Fabric builds and does not occur on the old architecture, because the two architectures batch prop updates differently.

Repro:

  • New Architecture (Fabric) enabled, iOS.
  • A Picker whose selectedValue is a controlled React state value updated from onValueChange.
  • Flick the wheel fast and repeatedly.

Root cause

Every committed selectedValue flows into -[RNCPicker setSelectedIndex:], which calls -selectRow:animated:. During a fling, React state lags the live native wheel and echoes back a now-stale index. That stale value re-drives selectRow: in the middle of the active gesture, re-entering UIPickerView's suspended update cycle while UIKit is hit-testing — hence the crash. On the old architecture the update batching happened differently, so only New Architecture builds are affected.

Fix

  • Add an -isUserScrolling helper that walks the picker's internal subview hierarchy and returns whether any backing UIScrollView is currently isTracking, isDragging, or isDecelerating.
  • Add an early return at the top of -setSelectedIndex: that skips the programmatic re-drive while the user is actively manipulating the wheel.

-didSelectRow: already keeps _selectedIndex in sync with the user's gesture, so no selection state is lost; programmatic resets performed while the wheel is idle continue to behave exactly as before. The change is confined to ios/RNCPicker.mm and does not alter the JS API.

Testing

This change has been running in a shipping iOS app built with the New Architecture, where it eliminated a recurring EXC_BAD_ACCESS inside UIKit's _endSuspendingUpdates during fast scrolling of a controlled picker.

  • Idle programmatic updates to selectedValue continue to move the wheel as expected on both the old and New Architecture.
  • The change is confined to ios/RNCPicker.mm and does not alter the JS API, so the existing JS test suite is unaffected; the repo has no Objective-C unit-test harness to extend, so no automated test was added.

This fix was prepared with AI assistance and reviewed by a human before submission.

On the New Architecture (Fabric), a controlled Picker can crash with
EXC_BAD_ACCESS during a fast fling. Every committed value flows through
-setSelectedIndex:, which calls -selectRow:animated:. Mid-gesture, lagging
React state echoes back a stale index that re-drives the live wheel,
re-entering UIPickerView's suspended update cycle during UIKit hit-testing.

Add an -isUserScrolling helper that inspects the picker's backing
UIScrollView(s) (isTracking/isDragging/isDecelerating) and bail out of
-setSelectedIndex: while the user is actively manipulating the wheel.
-didSelectRow: already keeps the internal index accurate, so idle
programmatic resets continue to work.
@raid5
raid5 requested a review from Naturalclar as a code owner July 17, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant