diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index d41fe7cd3fd..710f845589c 100644 --- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -28,7 +28,7 @@ using namespace facebook::react; @implementation RCTTextInputComponentView { TextInputShadowNode::ConcreteState::Shared _state; UIView *_backedTextInputView; - size_t _stateRevision; + BOOL _ignoreStateUpdate; } - (instancetype)initWithFrame:(CGRect)frame @@ -41,7 +41,7 @@ using namespace facebook::react; _backedTextInputView = props.traits.multiline ? [[RCTUITextView alloc] init] : [[RCTUITextField alloc] init]; _backedTextInputView.frame = self.bounds; _backedTextInputView.textInputDelegate = self; - _stateRevision = State::initialRevisionValue; + _ignoreStateUpdate = NO; [self addSubview:_backedTextInputView]; } @@ -166,10 +166,9 @@ using namespace facebook::react; return; } - if (_state->getRevision() != _stateRevision) { + if (!_ignoreStateUpdate) { auto data = _state->getData(); - _stateRevision = _state->getRevision(); - _backedTextInputView.attributedText = RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox); + [self _setAttributedString:RCTNSAttributedStringFromAttributedStringBox(data.attributedStringBox)]; } } @@ -184,12 +183,18 @@ using namespace facebook::react; RCTUIEdgeInsetsFromEdgeInsets(layoutMetrics.contentInsets - layoutMetrics.borderWidth); } +- (void)_setAttributedString:(NSAttributedString *)attributedString +{ + UITextRange *selectedRange = [_backedTextInputView selectedTextRange]; + _backedTextInputView.attributedText = attributedString; + [_backedTextInputView setSelectedTextRange:selectedRange notifyDelegate:NO]; +} + - (void)prepareForRecycle { [super prepareForRecycle]; _backedTextInputView.attributedText = [[NSAttributedString alloc] init]; _state.reset(); - _stateRevision = State::initialRevisionValue; } #pragma mark - RCTComponentViewProtocol @@ -328,8 +333,9 @@ using namespace facebook::react; auto data = _state->getData(); data.attributedStringBox = RCTAttributedStringBoxFromNSAttributedString(attributedString); + _ignoreStateUpdate = YES; _state->updateState(std::move(data), EventPriority::SynchronousUnbatched); - _stateRevision = _state->getRevision() + 1; + _ignoreStateUpdate = NO; } - (AttributedString::Range)_selectionRange @@ -379,7 +385,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]; } diff --git a/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp b/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp index 0912e891324..38ab5d2c7ae 100644 --- a/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp +++ b/ReactCommon/fabric/components/textinput/iostextinput/TextInputShadowNode.cpp @@ -69,9 +69,19 @@ void TextInputShadowNode::setTextLayoutManager( void TextInputShadowNode::updateStateIfNeeded() { ensureUnsealed(); - if (!getState() || getState()->getRevision() == State::initialRevisionValue) { + auto attributedStringFromJS = getAttributedString(); + bool hasJSUpdatedAttributedString = false; + if (getState()) { + hasJSUpdatedAttributedString = + attributedStringFromJS.compareTextAttributesWithoutFrame( + getStateData().lastAttributedStringFromJS); + } + + if (!getState() || getState()->getRevision() == State::initialRevisionValue || + hasJSUpdatedAttributedString) { auto state = TextInputState{}; - state.attributedStringBox = AttributedStringBox{getAttributedString()}; + state.attributedStringBox = AttributedStringBox{attributedStringFromJS}; + state.lastAttributedStringFromJS = attributedStringFromJS; state.paragraphAttributes = getConcreteProps().paragraphAttributes; state.layoutManager = textLayoutManager_; setStateData(std::move(state)); diff --git a/ReactCommon/fabric/components/textinput/iostextinput/TextInputState.h b/ReactCommon/fabric/components/textinput/iostextinput/TextInputState.h index 7c6773c8477..57ec10d2614 100644 --- a/ReactCommon/fabric/components/textinput/iostextinput/TextInputState.h +++ b/ReactCommon/fabric/components/textinput/iostextinput/TextInputState.h @@ -28,6 +28,11 @@ class TextInputState final { */ AttributedStringBox attributedStringBox; + /* + * Last attributed string that came from JavaScript. + */ + AttributedString lastAttributedStringFromJS; + /* * Represents all visual attributes of a paragraph of text represented as * a ParagraphAttributes.