fix(ios): avoid incorrectly updating caret position (#50680)

Summary:
Avoid incorrectly updating caret position

Pull Request resolved: https://github.com/facebook/react-native/issues/50641

The caret position is updated incorrectly when a user is first typing if an zero-length selection is set.

[IOS] [CHANGED] - Typing into TextInput now will not cause the caret position to update to the beginning when a zero-length selection is set.

Pull Request resolved: https://github.com/facebook/react-native/pull/50680

Test Plan:
Tested with the following code(a simplified version from the code in https://github.com/facebook/react-native/issues/50641)
```js
const [selection, setSelection] = useState({start: -1, end: -1});
const onSelectionChange = (
      evt: NativeSyntheticEvent<TextInputSelectionChangeEventData>,
    ) => {
      const {selection} = evt.nativeEvent;
      const {start, end} = selection;
      console.log('selection change: ', start, end);
      setSelection(selection);
};
return (
  <View style={{ position: 'absolute', top: 50, left: 30 }}>
    <TextInput
      placeholder="test"
      selection={selection}
      onSelectionChange={onSelectionChange}
    />
  </View>
);
```
When using the main branch, the caret position will jump back to the beginning after the first typing.
It works fine after applying this commit.

Reviewed By: fabriziocucci

Differential Revision: D72957245

Pulled By: cipolleschi

fbshipit-source-id: 3586797332b35e86b17f386a35e7d192ff758f7e
This commit is contained in:
Wang Chuan
2025-04-14 15:42:35 +01:00
committed by Fabrizio Cucci
parent f3c280442d
commit 89f4dd2a24
@@ -698,6 +698,11 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
}
- (void)_restoreTextSelection
{
[self _restoreTextSelectionAndIgnoreCaretChange:NO];
}
- (void)_restoreTextSelectionAndIgnoreCaretChange:(BOOL)ignore
{
const auto &selection = static_cast<const TextInputProps &>(*_props).selection;
if (!selection.has_value()) {
@@ -707,6 +712,9 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
offset:selection->start];
auto end = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument offset:selection->end];
auto range = [_backedTextInputView textRangeFromPosition:start toPosition:end];
if (ignore && range.empty) {
return;
}
[_backedTextInputView setSelectedTextRange:range notifyDelegate:YES];
}
@@ -721,19 +729,20 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
// Updating the UITextView attributedText, for example changing the lineHeight, the color or adding
// a new paragraph with \n, causes the cursor to move to the end of the Text and scroll.
// This is fixed by restoring the cursor position and scrolling to that position (iOS issue 652653).
if (selectedRange.empty) {
// Maintaining a cursor position relative to the end of the old text.
NSInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedRange.start];
NSInteger offsetFromEnd = oldTextLength - offsetStart;
NSInteger newOffset = attributedString.string.length - offsetFromEnd;
UITextPosition *position = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:newOffset];
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)];
}
[self _restoreTextSelection];
// Maintaining a cursor position relative to the end of the old text.
NSInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument
toPosition:selectedRange.start];
NSInteger offsetFromEnd = oldTextLength - offsetStart;
NSInteger newOffset = attributedString.string.length - offsetFromEnd;
UITextPosition *position = [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
offset:newOffset];
[_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
notifyDelegate:YES];
[_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)];
// A zero-length selection range can cause the caret position to change on iOS,
// and we have already updated the caret position, so we can safely ignore caret changing in this place.
[self _restoreTextSelectionAndIgnoreCaretChange:YES];
[self _updateTypingAttributes];
_lastStringStateWasUpdatedWith = attributedString;
}