[CP (beta)] [ios17][text_input]fix ios 17.0 keyboard freeze when switching languages (#48041)
CP for https://github.com/flutter/engine/pull/47566
*List which issues are fixed by this PR. You must list at least one issue.*
https://github.com/flutter/flutter/issues/134716
*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
index f02ab85..09946dc 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
@@ -602,7 +602,7 @@
case UITextGranularityLine:
// The default UITextInputStringTokenizer does not handle line granularity
// correctly. We need to implement our own line tokenizer.
- result = [self lineEnclosingPosition:position];
+ result = [self lineEnclosingPosition:position inDirection:direction];
break;
case UITextGranularityCharacter:
case UITextGranularityWord:
@@ -618,7 +618,21 @@
return result;
}
-- (UITextRange*)lineEnclosingPosition:(UITextPosition*)position {
+- (UITextRange*)lineEnclosingPosition:(UITextPosition*)position
+ inDirection:(UITextDirection)direction {
+ // TODO(hellohuanlin): remove iOS 17 check. The same logic should apply to older iOS version.
+ if (@available(iOS 17.0, *)) {
+ // According to the API doc if the text position is at a text-unit boundary, it is considered
+ // enclosed only if the next position in the given direction is entirely enclosed. Link:
+ // https://developer.apple.com/documentation/uikit/uitextinputtokenizer/1614464-rangeenclosingposition?language=objc
+ FlutterTextPosition* flutterPosition = (FlutterTextPosition*)position;
+ if (flutterPosition.index > _textInputView.text.length ||
+ (flutterPosition.index == _textInputView.text.length &&
+ direction == UITextStorageDirectionForward)) {
+ return nil;
+ }
+ }
+
// Gets the first line break position after the input position.
NSString* textAfter = [_textInputView
textInRange:[_textInputView textRangeFromPosition:position
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
index f18357a..8a6cc6b 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm
@@ -2660,6 +2660,54 @@
XCTAssertEqual(range.range.length, 20u);
}
+- (void)testFlutterTokenizerLineEnclosingEndOfDocumentInBackwardDirectionShouldNotReturnNil {
+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
+ [inputView insertText:@"0123456789\n012345"];
+ id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
+
+ FlutterTextRange* range =
+ (FlutterTextRange*)[tokenizer rangeEnclosingPosition:[inputView endOfDocument]
+ withGranularity:UITextGranularityLine
+ inDirection:UITextStorageDirectionBackward];
+ XCTAssertEqual(range.range.location, 11u);
+ XCTAssertEqual(range.range.length, 6u);
+}
+
+- (void)testFlutterTokenizerLineEnclosingEndOfDocumentInForwardDirectionShouldReturnNilOnIOS17 {
+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
+ [inputView insertText:@"0123456789\n012345"];
+ id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
+
+ FlutterTextRange* range =
+ (FlutterTextRange*)[tokenizer rangeEnclosingPosition:[inputView endOfDocument]
+ withGranularity:UITextGranularityLine
+ inDirection:UITextStorageDirectionForward];
+ if (@available(iOS 17.0, *)) {
+ XCTAssertNil(range);
+ } else {
+ XCTAssertEqual(range.range.location, 11u);
+ XCTAssertEqual(range.range.length, 6u);
+ }
+}
+
+- (void)testFlutterTokenizerLineEnclosingOutOfRangePositionShouldReturnNilOnIOS17 {
+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
+ [inputView insertText:@"0123456789\n012345"];
+ id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
+
+ FlutterTextPosition* position = [FlutterTextPosition positionWithIndex:100];
+ FlutterTextRange* range =
+ (FlutterTextRange*)[tokenizer rangeEnclosingPosition:position
+ withGranularity:UITextGranularityLine
+ inDirection:UITextStorageDirectionForward];
+ if (@available(iOS 17.0, *)) {
+ XCTAssertNil(range);
+ } else {
+ XCTAssertEqual(range.range.location, 0u);
+ XCTAssertEqual(range.range.length, 0u);
+ }
+}
+
- (void)testFlutterTextInputPluginRetainsFlutterTextInputView {
FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine];