diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index c206e7a5a6513..1cafc6ffcdc48 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -1676,6 +1676,16 @@ - (CGRect)firstRectForRange:(UITextRange*)range { } } + // The iOS 16 system highlight does not repect the height returned by `firstRectForRange` + // API (unlike iOS 17). So we return CGRectZero to hide it (unless if scribble is enabled). + // To support scribble's advanced gestures (e.g. insert a space with a vertical bar), + // at least 1 character's width is required. + if (@available(iOS 17, *)) { + // No-op + } else if (![self isScribbleAvailable]) { + return CGRectZero; + } + NSUInteger first = start; if (end < start) { first = end; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index f98571d5c279f..915241af18ec2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -1558,19 +1558,19 @@ - (void)testUpdateFirstRectForRange { [inputView firstRectForRange:range])); } -- (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight { +- (void)testFirstRectForRangeReturnsNoneZeroRectWhenScribbleIsEnabled { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin]; [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; + FlutterTextInputView* mockInputView = OCMPartialMock(inputView); + OCMStub([mockInputView isScribbleAvailable]).andReturn(YES); + [inputView setSelectionRects:@[ [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U], [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U], [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U], ]]; - FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; - XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), - [inputView firstRectForRange:singleRectRange])); FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)]; @@ -1581,6 +1581,34 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight { XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), [inputView firstRectForRange:multiRectRange])); } +} + +- (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight { + FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin]; + [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; + + [inputView setSelectionRects:@[ + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U], + [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U], + ]]; + FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; + if (@available(iOS 17, *)) { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), + [inputView firstRectForRange:singleRectRange])); + } else { + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange])); + } + + FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)]; + + if (@available(iOS 17, *)) { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100), + [inputView firstRectForRange:multiRectRange])); + } else { + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); + } [inputView setTextInputState:@{@"text" : @"COM"}]; FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange:NSMakeRange(3, 1)]; @@ -1598,16 +1626,19 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineRightToLeft { [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:3U], ]]; FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; - XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), - [inputView firstRectForRange:singleRectRange])); + if (@available(iOS 17, *)) { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), + [inputView firstRectForRange:singleRectRange])); + } else { + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange])); + } FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)]; if (@available(iOS 17, *)) { XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } [inputView setTextInputState:@{@"text" : @"COM"}]; @@ -1630,8 +1661,12 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight { [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 100, 100, 100) position:7U], ]]; FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; - XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), - [inputView firstRectForRange:singleRectRange])); + if (@available(iOS 17, *)) { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), + [inputView firstRectForRange:singleRectRange])); + } else { + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange])); + } FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)]; @@ -1639,8 +1674,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight { XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1659,16 +1693,19 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnMultipleLinesRightToLeft { [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:7U], ]]; FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)]; - XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), - [inputView firstRectForRange:singleRectRange])); + if (@available(iOS 17, *)) { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), + [inputView firstRectForRange:singleRectRange])); + } else { + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange])); + } FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)]; if (@available(iOS 17, *)) { XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1691,8 +1728,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMax XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, -10, 300, 120), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 10, 100, 80), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1715,8 +1751,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMax XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, -10, 300, 120), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, -10, 100, 120), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1739,8 +1774,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThre XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1763,8 +1797,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThre XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1787,8 +1820,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresho XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 400, 140), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } } @@ -1811,8 +1843,7 @@ - (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresho XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 400, 140), [inputView firstRectForRange:multiRectRange])); } else { - XCTAssertTrue(CGRectEqualToRect(CGRectMake(300, 0, 100, 100), - [inputView firstRectForRange:multiRectRange])); + XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange])); } }