Fix loss of negative text selection ranges (#19785)
iOS now matches Android behavior in that empty selections put the cursor at the start of the field.
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
index dc85fbe..7130469 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
+++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm
@@ -357,7 +357,21 @@
selectedRange.length != oldSelectedRange.length) {
needsEditingStateUpdate = YES;
[self.inputDelegate selectionWillChange:self];
- [self setSelectedTextRangeLocal:[FlutterTextRange rangeWithNSRange:selectedRange]];
+
+ // The state may contain an invalid selection, such as when no selection was
+ // explicitly set in the framework. This is handled here by setting the
+ // selection to (0,0). In contrast, Android handles this situation by
+ // clearing the selection, but the result in both cases is that the cursor
+ // is placed at the beginning of the field.
+ bool selectionBaseIsValid = selectionBase > 0 && selectionBase <= ((NSInteger)self.text.length);
+ bool selectionExtentIsValid =
+ selectionExtent > 0 && selectionExtent <= ((NSInteger)self.text.length);
+ if (selectionBaseIsValid && selectionExtentIsValid) {
+ [self setSelectedTextRangeLocal:[FlutterTextRange rangeWithNSRange:selectedRange]];
+ } else {
+ [self setSelectedTextRangeLocal:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 0)]];
+ }
+
_selectionAffinity = _kTextAffinityDownstream;
if ([state[@"selectionAffinity"] isEqualToString:@(_kTextAffinityUpstream)])
_selectionAffinity = _kTextAffinityUpstream;
diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
index 2a33bee..b2e6981 100644
--- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
+++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m
@@ -147,6 +147,26 @@
OCMReject([engine updateEditingClient:0 withState:[OCMArg any]]);
}
+- (void)testUpdateEditingClientNegativeSelection {
+ FlutterTextInputView* inputView = [[FlutterTextInputView alloc] init];
+ inputView.textInputDelegate = engine;
+
+ [inputView.text setString:@"SELECTION"];
+ inputView.markedTextRange = nil;
+ inputView.selectedTextRange = nil;
+
+ [inputView setTextInputState:@{
+ @"text" : @"SELECTION",
+ @"selectionBase" : @-1,
+ @"selectionExtent" : @-1
+ }];
+ OCMVerify([engine updateEditingClient:0
+ withState:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) {
+ return ([state[@"selectionBase"] intValue]) == 0 &&
+ ([state[@"selectionExtent"] intValue] == 0);
+ }]]);
+}
+
- (void)testAutofillInputViews {
NSDictionary* template = @{
@"inputType" : @{@"name" : @"TextInuptType.text"},