[iOS][Fabric] TextInput selectionColor (caret/selection tint) lost when backing view is recreated (multiline change), falls back to systemBlue
Needs: Author FeedbackNeeds: Repro
## 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 条评论