From 0ef63d0cacdb3f72fc3e996f73b499b3a7c6aa31 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Thu, 9 Apr 2020 03:41:22 -0700 Subject: [PATCH] Maintain selection and cursor location when setting string on TextInput Summary: Changelog: [Internal] Calling `_backedTextInputView.attributedText = attributedString` causes cursor to be moved to the end of text input. This applies to both, `UITextField` and `UITextView`. This is not desired as when JS sets a new text, we don't want the cursor to be moved to the end of text input. JS has the option to use view commands if it wishes to move cursor somewhere. Reviewed By: JoshuaGross Differential Revision: D20836201 fbshipit-source-id: 9234e54cfbc5fc206f723626988e505275788aae --- .../TextInput/RCTTextInputComponentView.mm | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index cdda94fe2d1..ee54e9de1c5 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -183,7 +183,7 @@ using namespace facebook::react; if (_state->getRevision() != _stateRevision) { auto data = _state->getData(); _stateRevision = _state->getRevision(); - _backedTextInputView.attributedText = RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox); + [self _setAttributedString:RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox)]; } } @@ -198,6 +198,25 @@ using namespace facebook::react; RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.contentInsets - layoutMetrics.borderWidth); } +- (void)_setAttributedString:(NSAttributedString *)attributedString +{ + UITextRange *selectedRange = [_backedTextInputView selectedTextRange]; + _backedTextInputView.attributedText = attributedString; + // Calling `[_backedTextInputView setAttributedText]` results + // in `textInputDidChangeSelection` being called but not `textInputDidChange`. + // For `_ignoreNextTextInputCall` to have correct value, these calls + // need to be balanced, that's why we manually set the flag here. + _ignoreNextTextInputCall = NO; + if (_lastStringStateWasUpdatedWith.length == attributedString.length) { + // Calling `[_backedTextInputView setAttributedText]` moves caret + // to the end of text input field. This cancels any selection as well + // as position in the text input field. In case the length of string + // doesn't change, selection and caret position is maintained. + [_backedTextInputView setSelectedTextRange:selectedRange notifyDelegate:NO]; + } + _lastStringStateWasUpdatedWith = attributedString; +} + - (void)prepareForRecycle { [super prepareForRecycle]; @@ -406,7 +425,7 @@ using namespace facebook::react; [[NSMutableAttributedString alloc] initWithAttributedString:_backedTextInputView.attributedText]; [mutableString replaceCharactersInRange:NSMakeRange(0, _backedTextInputView.attributedText.length) withString:value]; - _backedTextInputView.attributedText = mutableString; + [self _setAttributedString:mutableString]; [self _updateState]; }