ITADN

[iOS][Fabric] TextInput selectionColor (caret/selection tint) lost when backing view is recreated (multiline change), falls back to systemBlue

#57105Openkai-cupist 创建于 2026-06-08
Needs: Author FeedbackNeeds: Repro
K
kai-cupistcommented
## Description On iOS with the New Architecture (Fabric), a `TextInput`'s `selectionColor` can be silently lost, causing the caret / selection highlight / selection handles to render with the iOS default `systemBlue` instead of the provided color. On iOS, `selectionColor` is mapped to the backing view's `tintColor` (which drives the caret, selection highlight and grabber handles). Under Fabric this tint can end up `nil` after the backing view is recreated, so it resolves to the window's default tint (`systemBlue`). The failure is intermittent, and once it happens it tends to stay broken for other inputs (which reuse recycled component views) until the app is restarted. This is the `TextInput` counterpart of the already-reported Fabric/iOS `tintColor`-not-applied bugs for `RefreshControl` (#56343, #53987). ## Root cause `selectionColor` is applied to `tintColor` only behind a diff guard in `RCTTextInputComponentView.mm` (`updateProps:oldProps:`): ```objc // React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm if (newTextInputProps.selectionColor != oldTextInputProps.selectionColor) { _backedTextInputView.tintColor = RCTUIColorFromSharedColor(newTextInputProps.selectionColor); } ``` When `multiline` changes, the backing view is recreated in `_setMultiline:`: ```objc UIView<RCTBackedTextInputViewProtocol> *backedTextInputView = multiline ? [RCTUITextView new] : [RCTUITextField new]; RCTCopyBackedTextInput(_backedTextInputView, backedTextInputView); _backedTextInputView = backedTextInputView; ``` `RCTCopyBackedTextInput` (`RCTTextInputUtils.mm`) copies ~25 properties from the old backing view to the new one **but does not copy `tintColor`**. So the freshly created backing view starts with `tintColor == nil`. Because `selectionColor` itself did not change, the diff-guarded assignment above is skipped, leaving `tintColor == nil`, which resolves to the window default tint (`systemBlue`). In addition, `prepareForRecycle` does not reset or re-assert `tintColor`. Since Fabric recycles `RCTTextInputComponentView` instances across screens, once an instance ends up with `tintColor == nil` while `_props.selectionColor` already equals the incoming value, the diff guard keeps skipping reassignment and the view keeps rendering `systemBlue` until the recycle pool is torn down (app restart). This matches the observed "intermittent, then global until restart" behavior. Note: `RCTCopyBackedTextInput` on `main` still does not copy `tintColor`. ## Steps to reproduce 1. New Architecture (Fabric) enabled, iOS. 2. Render multiple `TextInput`s that all set the same `selectionColor` (e.g. `selectionColor="#773BEB"`), including at least one single-line and one multiline input, across different screens. 3. Navigate back and forth between screens that mount/unmount these inputs so Fabric recycles the backing component views (equivalently: toggle a `TextInput`'s `multiline` on a reused view). 4. Observe the caret / selection color: intermittently it renders the iOS default `systemBlue` instead of the provided `selectionColor`; once it does, subsequently mounted inputs also show `systemBlue` until the app is restarted. ## React Native Version 0.81.5 (root cause also present on `main`) ## Affected Platforms Runtime - iOS, Architecture - Fabric (New Architecture) ## Expected Results The caret, selection highlight and handles use the provided `selectionColor`. ## Actual Results After the backing view is recreated, the caret/selection render with the iOS default `systemBlue`, persisting across inputs until the app is restarted. ## Proposed fix Either (or both): - Copy `tintColor` in `RCTCopyBackedTextInput`, so a recreated backing view keeps the tint. - Re-assert `_backedTextInputView.tintColor` from `selectionColor` after `_setMultiline:` (and/or in `prepareForRecycle`) rather than only on a `selectionColor` diff. ## Related issues - #56343 — `[Fabric][iOS] RefreshControl tintColor/title props not applied on initial mount` - #53987 — `RefreshControl tintColor not respected & stuck on navigation` (same stack: New Arch, RN 0.81.x, Expo 54, iOS)
1 条评论