Skip to content

Commit

Permalink
[iOS][TextInput] Apply the fix for CJK languages on single-line text …
Browse files Browse the repository at this point in the history
…fields.
  • Loading branch information
mandrigin committed Dec 6, 2018
1 parent 9d00d4d commit f2ee775
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 32 deletions.
47 changes: 16 additions & 31 deletions Libraries/Text/TextInput/Multiline/RCTUITextView.m
Original file line number Diff line number Diff line change
Expand Up @@ -110,24 +110,25 @@ - (void)setTextAlignment:(NSTextAlignment)textAlignment

- (void)setAttributedText:(NSAttributedString *)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];
}
}

[super setAttributedText:attributedText];
[self textDidChange];
}

#pragma mark - Fix for CJK languages
// Search for `GH-19339` to see all code affected by this fix.

- (void)copyAttributesFrom:(NSAttributedString *)sourceString
{
[self.textStorage beginEditing];
NSTextStorage *textStorage = self.textStorage;
[sourceString enumerateAttributesInRange:NSMakeRange(0, sourceString.length)
options:NSAttributedStringEnumerationReverse
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
[textStorage setAttributes:attrs range:range];
}];
[self.textStorage endEditing];
}

#pragma mark - Overrides

- (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
Expand Down Expand Up @@ -255,20 +256,4 @@ - (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<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
[textStorage setAttributes:attrs range:range];
}];

[self.textStorage endEditing];
}

@end
7 changes: 7 additions & 0 deletions Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ NS_ASSUME_NONNULL_BEGIN
// Use `attributedText.string` instead.
@property (nonatomic, copy, nullable) NSString *text NS_UNAVAILABLE;

// This operation copies only the attributes from `attributedText`.
// It should be implemented in a way that doesn't break internal state of the text view.
// See more at: https://github.com/facebook/react-native/issues/19339
//
// Search for `GH-19339` to see all code affected by this fix.
- (void)copyAttributesFrom:(NSAttributedString *)attributedText;

@end

NS_ASSUME_NONNULL_END
24 changes: 23 additions & 1 deletion Libraries/Text/TextInput/RCTBaseTextInputView.m
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText
UITextRange *selection = self.backedTextInputView.selectedTextRange;
NSInteger oldTextLength = self.backedTextInputView.attributedText.string.length;

self.backedTextInputView.attributedText = attributedText;
[self safeSetAttributedText:attributedText];

if (selection.empty) {
// Maintaining a cursor position relative to the end of the old text.
Expand All @@ -160,6 +160,28 @@ - (void)setAttributedText:(NSAttributedString *)attributedText
}
}

- (void)safeSetAttributedText:(NSAttributedString *)attributedText {
/*
// Search for `GH-19339` to see all code affected by this fix.
//
// Using `setAttributedString:` while user is typing breaks text field's external state
// 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 (![self.backedTextInputView.attributedText.string isEqualToString:attributedText.string]) {
self.backedTextInputView.attributedText = attributedText;
} else {
// But if the text is preserved, we just copying the attributes from the source string.
// This operation doesn't reset textView's internal state.
if (![self.backedTextInputView.attributedText isEqualToAttributedString:attributedText]) {
[self.backedTextInputView copyAttributesFrom:attributedText];
}
}
}

- (RCTTextSelection *)selection
{
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;
Expand Down
23 changes: 23 additions & 0 deletions Libraries/Text/TextInput/Singleline/RCTUITextField.m
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,29 @@ - (CGRect)caretRectForPosition:(UITextPosition *)position
return [super caretRectForPosition:position];
}

#pragma mark - Fix for CJK Languages
// Search for `GH-19339` to see all code affected by this fix.

- (void)setAttributedText:(NSAttributedString *)attributedText {
[_attributesHolder setAttributedString:attributedText];
[super setAttributedText:attributedText];
}

- (void)copyAttributesFrom:(NSAttributedString *)source {
[_attributesHolder setAttributedString:super.attributedText];
}

- (NSAttributedString *)attributedText {
// For the text contents `super.attributedText.string` is the source of truth.
// If it differs, we match our internal state to it.
if(![super.attributedText.string isEqualToString:_attributesHolder.string]) {
[_attributesHolder setAttributedString:super.attributedText];
}

// For the text attributes, our internal state is the source of truth.
return _attributesHolder;
}

#pragma mark - Positioning Overrides

- (CGRect)textRectForBounds:(CGRect)bounds
Expand Down

0 comments on commit f2ee775

Please sign in to comment.