diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index dd03c96ea91..e1cad8a3f1f 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -698,6 +698,11 @@ static NSSet *returnKeyTypesSet; } - (void)_restoreTextSelection +{ + [self _restoreTextSelectionAndIgnoreCaretChange:NO]; +} + +- (void)_restoreTextSelectionAndIgnoreCaretChange:(BOOL)ignore { const auto &selection = static_cast(*_props).selection; if (!selection.has_value()) { @@ -707,6 +712,9 @@ static NSSet *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 *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; }