diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index 0e2c656a32fe5..2d33fb9d6d22d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -7,6 +7,8 @@ #import #import +#include "unicode/uchar.h" + #include "flutter/fml/logging.h" #include "flutter/fml/platform/darwin/string_range_sanitization.h" @@ -1896,6 +1898,22 @@ - (void)deleteBackward { NSRange oldRange = ((FlutterTextRange*)oldSelectedRange).range; if (oldRange.location > 0) { NSRange newRange = NSMakeRange(oldRange.location - 1, 1); + + // We should check if the last character is a part of emoji. + // If so, we must delete the entire emoji to prevent the text from being malformed. + NSRange charRange = fml::RangeForCharacterAtIndex(self.text, oldRange.location - 1); + UChar32 codePoint; + BOOL gotCodePoint = [self.text getBytes:&codePoint + maxLength:sizeof(codePoint) + usedLength:NULL + encoding:NSUTF32StringEncoding + options:kNilOptions + range:charRange + remainingRange:NULL]; + if (gotCodePoint && u_hasBinaryProperty(codePoint, UCHAR_EMOJI)) { + newRange = NSMakeRange(charRange.location, oldRange.location - charRange.location); + } + _selectedTextRange = [[FlutterTextRange rangeWithNSRange:newRange] copy]; [oldSelectedRange release]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index bd656cb6cc3aa..bec7028df6354 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -406,6 +406,41 @@ - (void)testStandardEditActions { XCTAssertEqualObjects(substring, @"bbbbaaaabbbbaaaa"); } +- (void)testDeletingBackward { + NSDictionary* config = self.mutableTemplateCopy; + [self setClientId:123 configuration:config]; + NSArray* inputFields = self.installedInputViews; + FlutterTextInputView* inputView = inputFields[0]; + + [inputView insertText:@"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”ΰΈ΅ "]; + [inputView deleteBackward]; + [inputView deleteBackward]; + + // Thai vowel is removed. + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³ΰΈ”"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦πŸ‡ΊπŸ‡³"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text πŸ₯°"); + [inputView deleteBackward]; + + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€ text "); + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + [inputView deleteBackward]; + + XCTAssertEqualObjects(inputView.text, @"αžΉπŸ˜€"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @"ឹ"); + [inputView deleteBackward]; + XCTAssertEqualObjects(inputView.text, @""); +} + - (void)testPastingNonTextDisallowed { NSDictionary* config = self.mutableTemplateCopy; [self setClientId:123 configuration:config]; diff --git a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme index 48aa2903c3a41..b1341fc8d5c2a 100644 --- a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme +++ b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/xcshareddata/xcschemes/IosUnitTests.xcscheme @@ -78,6 +78,12 @@ ReferencedContainer = "container:IosUnitTests.xcodeproj"> + + + +