Skip to content

Commit ad7bd64

Browse files
committed
[iOS][TextInput] Apply the fix for CJK languages on single-line text fields.
1 parent 9d00d4d commit ad7bd64

File tree

4 files changed

+71
-32
lines changed

4 files changed

+71
-32
lines changed

Libraries/Text/TextInput/Multiline/RCTUITextView.m

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -110,24 +110,25 @@ - (void)setTextAlignment:(NSTextAlignment)textAlignment
110110

111111
- (void)setAttributedText:(NSAttributedString *)attributedText
112112
{
113-
// Using `setAttributedString:` while user is typing breaks some internal mechanics
114-
// when entering complex input languages such as Chinese, Korean or Japanese.
115-
// see: https://github.com/facebook/react-native/issues/19339
116-
117-
// We try to avoid calling this method as much as we can.
118-
// If the text has changed, there is nothing we can do.
119-
if (![super.attributedText.string isEqualToString:attributedText.string]) {
120-
[super setAttributedText:attributedText];
121-
} else {
122-
// But if the text is preserved, we just copying the attributes from the source string.
123-
if (![super.attributedText isEqualToAttributedString:attributedText]) {
124-
[self copyTextAttributesFrom:attributedText];
125-
}
126-
}
127-
113+
[super setAttributedText:attributedText];
128114
[self textDidChange];
129115
}
130116

117+
#pragma mark - Fix for CJK languages
118+
// Search for `GH-19339` to see all code affected by this fix.
119+
120+
- (void)copyAttributesFrom:(NSAttributedString *)sourceString
121+
{
122+
[self.textStorage beginEditing];
123+
NSTextStorage *textStorage = self.textStorage;
124+
[sourceString enumerateAttributesInRange:NSMakeRange(0, sourceString.length)
125+
options:NSAttributedStringEnumerationReverse
126+
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
127+
[textStorage setAttributes:attrs range:range];
128+
}];
129+
[self.textStorage endEditing];
130+
}
131+
131132
#pragma mark - Overrides
132133

133134
- (void)setSelectedTextRange:(UITextRange *)selectedTextRange notifyDelegate:(BOOL)notifyDelegate
@@ -255,20 +256,4 @@ - (void)invalidatePlaceholderVisibility
255256
_placeholderView.hidden = !isVisible;
256257
}
257258

258-
#pragma mark - Utility Methods
259-
260-
- (void)copyTextAttributesFrom:(NSAttributedString *)sourceString
261-
{
262-
[self.textStorage beginEditing];
263-
264-
NSTextStorage *textStorage = self.textStorage;
265-
[sourceString enumerateAttributesInRange:NSMakeRange(0, sourceString.length)
266-
options:NSAttributedStringEnumerationReverse
267-
usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange range, BOOL * _Nonnull stop) {
268-
[textStorage setAttributes:attrs range:range];
269-
}];
270-
271-
[self.textStorage endEditing];
272-
}
273-
274259
@end

Libraries/Text/TextInput/RCTBackedTextInputViewProtocol.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ NS_ASSUME_NONNULL_BEGIN
3838
// Use `attributedText.string` instead.
3939
@property (nonatomic, copy, nullable) NSString *text NS_UNAVAILABLE;
4040

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

4350
NS_ASSUME_NONNULL_END

Libraries/Text/TextInput/RCTBaseTextInputView.m

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText
138138
UITextRange *selection = self.backedTextInputView.selectedTextRange;
139139
NSInteger oldTextLength = self.backedTextInputView.attributedText.string.length;
140140

141-
self.backedTextInputView.attributedText = attributedText;
141+
[self safeSetAttributedText:attributedText];
142142

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

163+
- (void)safeSetAttributedText:(NSAttributedString *)attributedText {
164+
/*
165+
// Search for `GH-19339` to see all code affected by this fix.
166+
//
167+
// Using `setAttributedString:` while user is typing breaks text field's external state
168+
// when entering complex input languages such as Chinese, Korean or Japanese.
169+
// see: https://github.com/facebook/react-native/issues/19339
170+
// We try to avoid calling this method as much as we can.
171+
//
172+
// If the text has changed, there is nothing we can do.
173+
*/
174+
if (![self.backedTextInputView.attributedText.string isEqualToString:attributedText.string]) {
175+
self.backedTextInputView.attributedText = attributedText;
176+
} else {
177+
// But if the text is preserved, we just copying the attributes from the source string.
178+
// This operation doesn't reset textView's internal state.
179+
if (![self.backedTextInputView.attributedText isEqualToAttributedString:attributedText]) {
180+
[self.backedTextInputView copyAttributesFrom:attributedText];
181+
}
182+
}
183+
}
184+
163185
- (RCTTextSelection *)selection
164186
{
165187
id<RCTBackedTextInputViewProtocol> backedTextInputView = self.backedTextInputView;

Libraries/Text/TextInput/Singleline/RCTUITextField.m

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
@implementation RCTUITextField {
1616
RCTBackedTextFieldDelegateAdapter *_textInputDelegateAdapter;
17+
NSMutableAttributedString *_attributesHolder;
1718
}
1819

1920
- (instancetype)initWithFrame:(CGRect)frame
@@ -25,6 +26,7 @@ - (instancetype)initWithFrame:(CGRect)frame
2526
object:self];
2627

2728
_textInputDelegateAdapter = [[RCTBackedTextFieldDelegateAdapter alloc] initWithTextField:self];
29+
_attributesHolder = [[NSMutableAttributedString alloc] init];
2830
}
2931

3032
return self;
@@ -107,6 +109,29 @@ - (CGRect)caretRectForPosition:(UITextPosition *)position
107109
return [super caretRectForPosition:position];
108110
}
109111

112+
#pragma mark - Fix for CJK Languages
113+
// Search for `GH-19339` to see all code affected by this fix.
114+
115+
- (void)setAttributedText:(NSAttributedString *)attributedText {
116+
[_attributesHolder setAttributedString:attributedText];
117+
[super setAttributedText:attributedText];
118+
}
119+
120+
- (void)copyAttributesFrom:(NSAttributedString *)source {
121+
[_attributesHolder setAttributedString:super.attributedText];
122+
}
123+
124+
- (NSAttributedString *)attributedText {
125+
// For the text contents `super.attributedText.string` is the source of truth.
126+
// If it differs, we match our internal state to it.
127+
if(![super.attributedText.string isEqualToString:_attributesHolder.string]) {
128+
[_attributesHolder setAttributedString:super.attributedText];
129+
}
130+
131+
// For the text attributes, our internal state is the source of truth.
132+
return _attributesHolder;
133+
}
134+
110135
#pragma mark - Positioning Overrides
111136

112137
- (CGRect)textRectForBounds:(CGRect)bounds

0 commit comments

Comments
 (0)