[ios][ios17]fix auto correction highlight on top left corner (flutter/engine#44779)

Fix native auto-correction highlight region on top left corner. 

This PR uses the system auto-correction highlight on iOS 17, which was disabled by https://github.com/flutter/engine/pull/44354

<img width="479" alt="Screenshot 2023-08-16 at 1 19 39 PM" src="https://github.com/flutter/engine/assets/41930132/a5a1dda7-ba21-462e-a65c-1afeecf7559f">

*List which issues are fixed by this PR. You must list at least one issue.*

Fixes https://github.com/flutter/flutter/issues/131622
Fixes https://github.com/flutter/flutter/issues/131695
Fixes https://github.com/flutter/flutter/issues/130818

*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
This commit is contained in:
hellohuanlin
2023-08-17 09:48:06 -07:00
committed by GitHub
parent deaf5f2b5b
commit 380e7d7402
2 changed files with 102 additions and 6 deletions

View File

@@ -2449,18 +2449,32 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
}
- (void)setEditableSizeAndTransform:(NSDictionary*)dictionary {
[_activeView setEditableTransform:dictionary[@"transform"]];
NSArray* transform = dictionary[@"transform"];
[_activeView setEditableTransform:transform];
const int leftIndex = 12;
const int topIndex = 13;
if ([_activeView isScribbleAvailable]) {
// This is necessary to set up where the scribble interactable element will be.
int leftIndex = 12;
int topIndex = 13;
_inputHider.frame =
CGRectMake([dictionary[@"transform"][leftIndex] intValue],
[dictionary[@"transform"][topIndex] intValue], [dictionary[@"width"] intValue],
[dictionary[@"height"] intValue]);
CGRectMake([transform[leftIndex] intValue], [transform[topIndex] intValue],
[dictionary[@"width"] intValue], [dictionary[@"height"] intValue]);
_activeView.frame =
CGRectMake(0, 0, [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]);
_activeView.tintColor = [UIColor clearColor];
} else {
// TODO(hellohuanlin): Also need to handle iOS 16 case, where the auto-correction highlight does
// not match the size of text.
// See https://github.com/flutter/flutter/issues/131695
if (@available(iOS 17, *)) {
// Move auto-correction highlight to overlap with the actual text.
// This is to fix an issue where the system auto-correction highlight is displayed at
// the top left corner of the screen on iOS 17+.
// This problem also happens on iOS 16, but the size of highlight does not match the text.
// See https://github.com/flutter/flutter/issues/131695
// TODO(hellohuanlin): Investigate if we can use non-zero size.
_inputHider.frame =
CGRectMake([transform[leftIndex] intValue], [transform[topIndex] intValue], 0, 0);
}
}
}
@@ -2488,7 +2502,22 @@ static BOOL IsSelectionRectBoundaryCloserToPoint(CGPoint point,
? NSWritingDirectionLeftToRight
: NSWritingDirectionRightToLeft]];
}
BOOL shouldNotifyTextChange = NO;
if (@available(iOS 17, *)) {
// Force UIKit to query the selectionRects again on iOS 17+
// This is to fix a bug on iOS 17+ where UIKit queries the outdated selectionRects after
// entering a character, resulting in auto-correction highlight region missing the last
// character.
shouldNotifyTextChange = YES;
}
if (shouldNotifyTextChange) {
[_activeView.inputDelegate textWillChange:_activeView];
}
_activeView.selectionRects = rectsAsRect;
if (shouldNotifyTextChange) {
[_activeView.inputDelegate textDidChange:_activeView];
}
}
- (void)startLiveTextInput {

View File

@@ -61,6 +61,7 @@ FLUTTER_ASSERT_ARC
@interface FlutterTextInputPlugin ()
@property(nonatomic, assign) FlutterTextInputView* activeView;
@property(nonatomic, readonly) UIView* inputHider;
@property(nonatomic, readonly) UIView* keyboardViewContainer;
@property(nonatomic, readonly) UIView* keyboardView;
@property(nonatomic, assign) UIView* cachedFirstResponder;
@@ -422,6 +423,72 @@ FLUTTER_ASSERT_ARC
}
}
- (void)testInputHiderOverlapWithTextWhenScribbleIsDisabledAfterIOS17AndDoesNotOverlapBeforeIOS17 {
FlutterTextInputPlugin* myInputPlugin =
[[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
FlutterMethodCall* setClientCall =
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
arguments:@[ @(123), self.mutableTemplateCopy ]];
[myInputPlugin handleMethodCall:setClientCall
result:^(id _Nullable result){
}];
FlutterTextInputView* mockInputView = OCMPartialMock(myInputPlugin.activeView);
OCMStub([mockInputView isScribbleAvailable]).andReturn(NO);
// yOffset = 200.
NSArray* yOffsetMatrix = @[ @1, @0, @0, @0, @0, @1, @0, @0, @0, @0, @1, @0, @0, @200, @0, @1 ];
FlutterMethodCall* setPlatformViewClientCall =
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditableSizeAndTransform"
arguments:@{@"transform" : yOffsetMatrix}];
[myInputPlugin handleMethodCall:setPlatformViewClientCall
result:^(id _Nullable result){
}];
if (@available(iOS 17, *)) {
XCTAssert(CGRectEqualToRect(myInputPlugin.inputHider.frame, CGRectMake(0, 200, 0, 0)),
@"The input hider should overlap with the text on and after iOS 17");
} else {
XCTAssert(CGRectEqualToRect(myInputPlugin.inputHider.frame, CGRectZero),
@"The input hider should be on the origin of screen on and before iOS 16.");
}
}
- (void)testSetSelectionRectsNotifiesTextChangeAfterIOS17AndDoesNotNotifyBeforeIOS17 {
FlutterTextInputPlugin* myInputPlugin =
[[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
FlutterMethodCall* setClientCall =
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
arguments:@[ @(123), self.mutableTemplateCopy ]];
[myInputPlugin handleMethodCall:setClientCall
result:^(id _Nullable result){
}];
id mockInputDelegate = OCMProtocolMock(@protocol(UITextInputDelegate));
myInputPlugin.activeView.inputDelegate = mockInputDelegate;
NSArray<NSNumber*>* selectionRect = [NSArray arrayWithObjects:@0, @0, @100, @100, @0, @1, nil];
NSArray* selectionRects = [NSArray arrayWithObjects:selectionRect, nil];
FlutterMethodCall* methodCall =
[FlutterMethodCall methodCallWithMethodName:@"Scribble.setSelectionRects"
arguments:selectionRects];
[myInputPlugin handleMethodCall:methodCall
result:^(id _Nullable result){
}];
if (@available(iOS 17.0, *)) {
OCMVerify([mockInputDelegate textWillChange:myInputPlugin.activeView]);
OCMVerify([mockInputDelegate textDidChange:myInputPlugin.activeView]);
} else {
OCMVerify(never(), [mockInputDelegate textWillChange:myInputPlugin.activeView]);
OCMVerify(never(), [mockInputDelegate textDidChange:myInputPlugin.activeView]);
}
}
- (void)testTextRangeFromPositionMatchesUITextViewBehavior {
FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
FlutterTextPosition* fromPosition = [FlutterTextPosition positionWithIndex:2];