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 500d5cd8f4a..7168515b1a0 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 @@ -1690,6 +1690,16 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point, } } + // 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/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 108979c1a3a..faee42df1b4 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -1558,6 +1558,31 @@ FLUTTER_ASSERT_ARC [inputView firstRectForRange:range])); } +- (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* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)]; + + if (@available(iOS 17, *)) { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100), + [inputView firstRectForRange:multiRectRange])); + } else { + XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100), + [inputView firstRectForRange:multiRectRange])); + } +} + - (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight { FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin]; [inputView setTextInputState:@{@"text" : @"COMPOSING"}]; @@ -1569,8 +1594,12 @@ FLUTTER_ASSERT_ARC [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])); + 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)]; @@ -1578,8 +1607,7 @@ FLUTTER_ASSERT_ARC 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])); } [inputView setTextInputState:@{@"text" : @"COM"}]; @@ -1598,16 +1626,19 @@ FLUTTER_ASSERT_ARC [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 @@ FLUTTER_ASSERT_ARC [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 @@ FLUTTER_ASSERT_ARC 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 @@ FLUTTER_ASSERT_ARC [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 @@ FLUTTER_ASSERT_ARC 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 @@ FLUTTER_ASSERT_ARC 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 @@ FLUTTER_ASSERT_ARC 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 @@ FLUTTER_ASSERT_ARC 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 @@ FLUTTER_ASSERT_ARC 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 @@ FLUTTER_ASSERT_ARC 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])); } }