Skip to content

Commit

Permalink
Bug fix: <TextInput> content is reset when emoji is entered at the ma…
Browse files Browse the repository at this point in the history
…x length

Summary:
When maxLength is defined in <TextInput>, if the last character at max length is an emoji, the content of the input is cleared:

{F620865178} {F620865237}

Related Github issues:

#10929
#10964

## Root cause:

When NSString was created, unicode characters were 16-bit long, so Objective-C considers every unicode character as 16-bit. However, unicode was later extended to more than 16bit, for example, emojis, which causes NSString substring method cuts off at the wrong position.

Example:

```
NSString *s = @"abc{emoji:1f601}";
NSInteger len = s.length; //length is 5 (as {emoji:1f601} occupies two 16-bit characters)
NSString *s3 = [s substringToIndex: 3]; //s3 is "abc"
NSString *s4 = [s substringToIndex: 4]; //s4 is null!
```

If string s, "abc{emoji:1f601}", is entered in <TextInput>, which has max length 4, it will truncate the string to the first 4 characters, "cutting" the emoji in half which causes encoding error and returns null. The text input is cleared.

## Solution:

If the character at max length is longer than 16-bit, truncate the character BEFORE it instead. In the previous example, truncate till index 3 instead of 4. The end result will be "abc" and the emoji is dropped.

## Changelog:

[iOS] [Fixed] - <TextInput> content is reset when emoji is entered at the max length

Reviewed By: p-sun

Differential Revision: D28821909

fbshipit-source-id: 4720d864970b554160ed5388f65b352ce95a6199
  • Loading branch information
Shuhao Zhang authored and facebook-github-bot committed Jun 4, 2021
1 parent 41f145f commit f3b8d49
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Libraries/Text/TextInput/RCTBaseTextInputView.m
Expand Up @@ -397,6 +397,14 @@ - (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
if (text.length > allowedLength) {
// If we typed/pasted more than one character, limit the text inputted.
if (text.length > 1) {
if (allowedLength > 0) {
//make sure unicode characters that are longer than 16 bits (such as emojis) are not cut off
NSRange cutOffCharacterRange = [text rangeOfComposedCharacterSequenceAtIndex: allowedLength - 1];
if (cutOffCharacterRange.location + cutOffCharacterRange.length > allowedLength) {
//the character at the length limit takes more than 16bits, truncation should end at the character before
allowedLength = cutOffCharacterRange.location;
}
}
// Truncate the input string so the result is exactly maxLength
NSString *limitedString = [text substringToIndex:allowedLength];
NSMutableAttributedString *newAttributedText = [backedTextInputView.attributedText mutableCopy];
Expand Down
Expand Up @@ -335,6 +335,15 @@ - (NSString *)textInputShouldChangeText:(NSString *)text inRange:(NSRange)range
if (props.maxLength) {
NSInteger allowedLength = props.maxLength - _backedTextInputView.attributedText.string.length + range.length;

if (allowedLength > 0 && text.length > allowedLength) {
// make sure unicode characters that are longer than 16 bits (such as emojis) are not cut off
NSRange cutOffCharacterRange = [text rangeOfComposedCharacterSequenceAtIndex:allowedLength - 1];
if (cutOffCharacterRange.location + cutOffCharacterRange.length > allowedLength) {
// the character at the length limit takes more than 16bits, truncation should end at the character before
allowedLength = cutOffCharacterRange.location;
}
}

if (allowedLength <= 0) {
return nil;
}
Expand Down

0 comments on commit f3b8d49

Please sign in to comment.