From 89f4dd2a243dd1255bf2ced3a694ffc62b7aa3fd Mon Sep 17 00:00:00 2001 From: Wang Chuan Date: Mon, 14 Apr 2025 05:42:24 -0700 Subject: [PATCH] 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, ) => { const {selection} = evt.nativeEvent; const {start, end} = selection; console.log('selection change: ', start, end); setSelection(selection); }; return ( ); ``` 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 --- .../TextInput/RCTTextInputComponentView.mm | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) 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; }