diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index dc85fbee706..7130469ee7b 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -357,7 +357,21 @@ static NSString* uniqueIdFromDictionary(NSDictionary* dictionary) { 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/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m index 2a33beedbab..b2e6981d2b9 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.m @@ -147,6 +147,26 @@ FLUTTER_ASSERT_ARC 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"},