This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
[ios][iOS 17] fix auto-correction highlight on top left corner #44604
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,7 @@ @interface FlutterSecureTextInputView : FlutterTextInputView | |
|
||
@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; | ||
|
@@ -1426,44 +1427,53 @@ - (void)testUpdateFirstRectForRange { | |
@(-6.0), @(3.0), @(9.0), @(1.0) | ||
]; | ||
|
||
CGRect kInvalidFirstRectRelative = | ||
[textInputPlugin.viewController.view convertRect:kInvalidFirstRect toView:inputView]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wanna highlight the changes to this test case. Let me know if it makes sense @LongCatIsLooong |
||
|
||
// Invalid since we don't have the transform or the rect. | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range])); | ||
|
||
[inputView setEditableTransform:yOffsetMatrix]; | ||
// Invalid since we don't have the rect. | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range])); | ||
|
||
// Valid rect and transform. | ||
CGRect testRect = CGRectMake(0, 0, 100, 100); | ||
[inputView setMarkedRect:testRect]; | ||
|
||
CGRect finalRect = CGRectOffset(testRect, 0, 200); | ||
XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range])); | ||
CGRect finalRectRelative = [textInputPlugin.viewController.view convertRect:finalRect | ||
toView:inputView]; | ||
XCTAssertTrue(CGRectEqualToRect(finalRectRelative, [inputView firstRectForRange:range])); | ||
// Idempotent. | ||
XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(finalRectRelative, [inputView firstRectForRange:range])); | ||
|
||
// Use an invalid matrix: | ||
[inputView setEditableTransform:zeroMatrix]; | ||
// Invalid matrix is invalid. | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range])); | ||
|
||
// Revert the invalid matrix change. | ||
[inputView setEditableTransform:yOffsetMatrix]; | ||
[inputView setMarkedRect:testRect]; | ||
XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(finalRectRelative, [inputView firstRectForRange:range])); | ||
|
||
// Use an invalid rect: | ||
[inputView setMarkedRect:kInvalidFirstRect]; | ||
// Invalid marked rect is invalid. | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range])); | ||
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range])); | ||
|
||
// Use a 3d affine transform that does 3d-scaling, z-index rotating and 3d translation. | ||
[inputView setEditableTransform:affineMatrix]; | ||
[inputView setMarkedRect:testRect]; | ||
XCTAssertTrue( | ||
CGRectEqualToRect(CGRectMake(-306, 3, 300, 300), [inputView firstRectForRange:range])); | ||
|
||
CGRect relativeRect = | ||
[textInputPlugin.viewController.view convertRect:CGRectMake(-306, 3, 300, 300) | ||
toView:inputView]; | ||
|
||
XCTAssertTrue(CGRectEqualToRect(relativeRect, [inputView firstRectForRange:range])); | ||
|
||
NSAssert(inputView.superview, @"inputView is not in the view hierarchy!"); | ||
const CGPoint offset = CGPointMake(113, 119); | ||
|
@@ -1472,8 +1482,8 @@ - (void)testUpdateFirstRectForRange { | |
inputView.frame = currentFrame; | ||
// Moving the input view within the FlutterView shouldn't affect the coordinates, | ||
// since the framework sends us global coordinates. | ||
XCTAssertTrue(CGRectEqualToRect(CGRectMake(-306 - 113, 3 - 119, 300, 300), | ||
[inputView firstRectForRange:range])); | ||
CGRect target = CGRectOffset(relativeRect, -113, -119); | ||
XCTAssertTrue(CGRectEqualToRect(target, [inputView firstRectForRange:range])); | ||
} | ||
|
||
- (void)testFirstRectForRangeReturnsCorrectSelectionRect { | ||
|
@@ -2295,6 +2305,76 @@ - (void)testInitialActiveViewCantAccessTextInputDelegate { | |
XCTAssertNil(textInputPlugin.activeView.textInputDelegate); | ||
} | ||
|
||
- (void)testInputHiderIsOffScreenWhenScribbleIsDisabled { | ||
FlutterTextInputPlugin* myInputPlugin = | ||
[[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])]; | ||
myInputPlugin.viewController = viewController; | ||
|
||
NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes; | ||
XCTAssertEqual(scenes.count, 1UL, @"There must only be 1 scene for test"); | ||
UIScene* scene = scenes.anyObject; | ||
XCTAssert([scene isKindOfClass:[UIWindowScene class]], @"Must be a window scene for test"); | ||
UIWindowScene* windowScene = (UIWindowScene*)scene; | ||
XCTAssert(windowScene.windows.count > 0, @"There must be at least 1 window for test"); | ||
UIWindow* window = windowScene.windows[0]; | ||
[window addSubview:viewController.view]; | ||
[viewController loadView]; | ||
UIScreen* screen = viewController.flutterScreenIfViewLoaded; | ||
XCTAssertNotNil(screen, @"Screen must be present at this point"); | ||
|
||
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){ | ||
}]; | ||
|
||
CGRect offScreenRect = CGRectMake(0, -screen.bounds.size.height, 0, 0); | ||
XCTAssert(CGRectEqualToRect(myInputPlugin.inputHider.frame, offScreenRect), | ||
@"The input hider should stay offScreen if scribble is disabled."); | ||
} | ||
|
||
- (void)testInputHiderIsOnScreenWhenScribbleIsEnabled { | ||
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(YES); | ||
|
||
// 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){ | ||
}]; | ||
|
||
XCTAssertEqual(myInputPlugin.inputHider.frame.origin.y, 200, | ||
@"The input hider should be brought on screen if scribble is enabled"); | ||
} | ||
|
||
#pragma mark - Accessibility - Tests | ||
|
||
- (void)testUITextInputAccessibilityNotHiddenWhenShowed { | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@LongCatIsLooong I'm a bit iffy on this one and could use another pair of eyes:
My understanding of your previous PR is that this should return rects in FlutterTextInputView's coord space - not only valid rects, but also this dummy invalid rect (because otherwise an invalid rect could very well become a valid rect with some transforms of FlutterTextInputView).
Let me know if you wanna discuss offline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think
kInvalidFirstRect
is probably specified in the window's coordinate system so it needs to be transformed. The input view used to be placed at the top left corner of the window iirc.