diff --git a/Libraries/Text/TextInput/Multiline/RCTUITextView.m b/Libraries/Text/TextInput/Multiline/RCTUITextView.m index ddfd1c27f50d7b..7fc080a737f8c7 100644 --- a/Libraries/Text/TextInput/Multiline/RCTUITextView.m +++ b/Libraries/Text/TextInput/Multiline/RCTUITextView.m @@ -110,7 +110,21 @@ - (void)setTextAlignment:(NSTextAlignment)textAlignment - (void)setAttributedText:(NSAttributedString *)attributedText { - [super setAttributedText:attributedText]; + // Using `setAttributedString:` while user is typing breaks some internal mechanics + // when entering complex input languages such as Chinese, Korean or Japanese. + // see: https://github.com/facebook/react-native/issues/19339 + + // We try to avoid calling this method as much as we can. + // If the text has changed, there is nothing we can do. + if (![super.attributedText.string isEqualToString:attributedText.string]) { + [super setAttributedText:attributedText]; + } else { + // But if the text is preserved, we just copying the attributes from the source string. + if (![super.attributedText isEqualToAttributedString:attributedText]) { + [self copyTextAttributesFrom:attributedText]; + } + } + [self textDidChange]; } @@ -241,4 +255,20 @@ - (void)invalidatePlaceholderVisibility _placeholderView.hidden = !isVisible; } +#pragma mark - Utility Methods + +- (void)copyTextAttributesFrom:(NSAttributedString *)sourceString +{ + [self.textStorage beginEditing]; + + NSTextStorage *textStorage = self.textStorage; + [sourceString enumerateAttributesInRange:NSMakeRange(0, sourceString.length) + options:NSAttributedStringEnumerationReverse + usingBlock:^(NSDictionary * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) { + [textStorage setAttributes:attrs range:range]; + }]; + + [self.textStorage endEditing]; +} + @end