[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:
@@ -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 {
|
||||
|
||||
@@ -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];
|
||||
|
||||
Reference in New Issue
Block a user