Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a85b9bc

Browse files
committed
[ios]fix highlight on top left corner
1 parent f1f912c commit a85b9bc

File tree

2 files changed

+107
-17
lines changed

2 files changed

+107
-17
lines changed

shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,10 +1625,13 @@ - (CGRect)firstRectForRange:(UITextRange*)range {
16251625
NSUInteger start = ((FlutterTextPosition*)range.start).index;
16261626
NSUInteger end = ((FlutterTextPosition*)range.end).index;
16271627
if (_markedTextRange != nil) {
1628+
UIView* hostView = _textInputPlugin.hostView;
1629+
NSAssert(hostView == nil || [self isDescendantOfView:hostView], @"%@ is not a descendant of %@",
1630+
self, hostView);
16281631
// The candidates view can't be shown if the framework has not sent the
16291632
// first caret rect.
16301633
if (CGRectEqualToRect(kInvalidFirstRect, _markedRect)) {
1631-
return kInvalidFirstRect;
1634+
return hostView ? [hostView convertRect:kInvalidFirstRect toView:self] : kInvalidFirstRect;
16321635
}
16331636

16341637
if (CGRectEqualToRect(_cachedFirstRect, kInvalidFirstRect)) {
@@ -1642,9 +1645,6 @@ - (CGRect)firstRectForRange:(UITextRange*)range {
16421645
_cachedFirstRect = [self localRectFromFrameworkTransform:rect];
16431646
}
16441647

1645-
UIView* hostView = _textInputPlugin.hostView;
1646-
NSAssert(hostView == nil || [self isDescendantOfView:hostView], @"%@ is not a descendant of %@",
1647-
self, hostView);
16481648
return hostView ? [hostView convertRect:_cachedFirstRect toView:self] : _cachedFirstRect;
16491649
}
16501650

@@ -2461,6 +2461,16 @@ - (void)setEditableSizeAndTransform:(NSDictionary*)dictionary {
24612461
_activeView.frame =
24622462
CGRectMake(0, 0, [dictionary[@"width"] intValue], [dictionary[@"height"] intValue]);
24632463
_activeView.tintColor = [UIColor clearColor];
2464+
} else {
2465+
// Screen must be loaded at this point.
2466+
UIScreen* screen = _viewController.flutterScreenIfViewLoaded;
2467+
2468+
// Position FlutterTextInputView outside of the screen (if scribble is disabled).
2469+
// This is to fix a bug where native auto-correction highlight is displayed on
2470+
// top left corner of the screen (See: https://github.com/flutter/flutter/issues/131695)
2471+
// and a bug where the native auto-correction suggestion menu displayed (See:
2472+
// https://github.com/flutter/flutter/issues/130818).
2473+
_inputHider.frame = CGRectMake(0, -screen.bounds.size.height, 0, 0);
24642474
}
24652475
}
24662476

shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ @interface FlutterSecureTextInputView : FlutterTextInputView
6161

6262
@interface FlutterTextInputPlugin ()
6363
@property(nonatomic, assign) FlutterTextInputView* activeView;
64+
@property(nonatomic, readonly) UIView* inputHider;
6465
@property(nonatomic, readonly) UIView* keyboardViewContainer;
6566
@property(nonatomic, readonly) UIView* keyboardView;
6667
@property(nonatomic, assign) UIView* cachedFirstResponder;
@@ -1426,44 +1427,53 @@ - (void)testUpdateFirstRectForRange {
14261427
@(-6.0), @(3.0), @(9.0), @(1.0)
14271428
];
14281429

1430+
CGRect kInvalidFirstRectRelative =
1431+
[textInputPlugin.viewController.view convertRect:kInvalidFirstRect toView:inputView];
1432+
14291433
// Invalid since we don't have the transform or the rect.
1430-
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1434+
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range]));
14311435

14321436
[inputView setEditableTransform:yOffsetMatrix];
14331437
// Invalid since we don't have the rect.
1434-
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1438+
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range]));
14351439

14361440
// Valid rect and transform.
14371441
CGRect testRect = CGRectMake(0, 0, 100, 100);
14381442
[inputView setMarkedRect:testRect];
14391443

14401444
CGRect finalRect = CGRectOffset(testRect, 0, 200);
1441-
XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1445+
CGRect finalRectRelative = [textInputPlugin.viewController.view convertRect:finalRect
1446+
toView:inputView];
1447+
XCTAssertTrue(CGRectEqualToRect(finalRectRelative, [inputView firstRectForRange:range]));
14421448
// Idempotent.
1443-
XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1449+
XCTAssertTrue(CGRectEqualToRect(finalRectRelative, [inputView firstRectForRange:range]));
14441450

14451451
// Use an invalid matrix:
14461452
[inputView setEditableTransform:zeroMatrix];
14471453
// Invalid matrix is invalid.
1448-
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1449-
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1454+
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range]));
1455+
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range]));
14501456

14511457
// Revert the invalid matrix change.
14521458
[inputView setEditableTransform:yOffsetMatrix];
14531459
[inputView setMarkedRect:testRect];
1454-
XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1460+
XCTAssertTrue(CGRectEqualToRect(finalRectRelative, [inputView firstRectForRange:range]));
14551461

14561462
// Use an invalid rect:
14571463
[inputView setMarkedRect:kInvalidFirstRect];
14581464
// Invalid marked rect is invalid.
1459-
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1460-
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1465+
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range]));
1466+
XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRectRelative, [inputView firstRectForRange:range]));
14611467

14621468
// Use a 3d affine transform that does 3d-scaling, z-index rotating and 3d translation.
14631469
[inputView setEditableTransform:affineMatrix];
14641470
[inputView setMarkedRect:testRect];
1465-
XCTAssertTrue(
1466-
CGRectEqualToRect(CGRectMake(-306, 3, 300, 300), [inputView firstRectForRange:range]));
1471+
1472+
CGRect relativeRect =
1473+
[textInputPlugin.viewController.view convertRect:CGRectMake(-306, 3, 300, 300)
1474+
toView:inputView];
1475+
1476+
XCTAssertTrue(CGRectEqualToRect(relativeRect, [inputView firstRectForRange:range]));
14671477

14681478
NSAssert(inputView.superview, @"inputView is not in the view hierarchy!");
14691479
const CGPoint offset = CGPointMake(113, 119);
@@ -1472,8 +1482,8 @@ - (void)testUpdateFirstRectForRange {
14721482
inputView.frame = currentFrame;
14731483
// Moving the input view within the FlutterView shouldn't affect the coordinates,
14741484
// since the framework sends us global coordinates.
1475-
XCTAssertTrue(CGRectEqualToRect(CGRectMake(-306 - 113, 3 - 119, 300, 300),
1476-
[inputView firstRectForRange:range]));
1485+
CGRect target = CGRectOffset(relativeRect, -113, -119);
1486+
XCTAssertTrue(CGRectEqualToRect(target, [inputView firstRectForRange:range]));
14771487
}
14781488

14791489
- (void)testFirstRectForRangeReturnsCorrectSelectionRect {
@@ -2295,6 +2305,76 @@ - (void)testInitialActiveViewCantAccessTextInputDelegate {
22952305
XCTAssertNil(textInputPlugin.activeView.textInputDelegate);
22962306
}
22972307

2308+
- (void)testInputHiderIsOffScreenWhenScribbleIsDisabled {
2309+
FlutterTextInputPlugin* myInputPlugin =
2310+
[[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
2311+
myInputPlugin.viewController = viewController;
2312+
2313+
NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
2314+
XCTAssertEqual(scenes.count, 1UL, @"There must only be 1 scene for test");
2315+
UIScene* scene = scenes.anyObject;
2316+
XCTAssert([scene isKindOfClass:[UIWindowScene class]], @"Must be a window scene for test");
2317+
UIWindowScene* windowScene = (UIWindowScene*)scene;
2318+
XCTAssert(windowScene.windows.count > 0, @"There must be at least 1 window for test");
2319+
UIWindow* window = windowScene.windows[0];
2320+
[window addSubview:viewController.view];
2321+
[viewController loadView];
2322+
UIScreen* screen = viewController.flutterScreenIfViewLoaded;
2323+
XCTAssertNotNil(screen, @"Screen must be present at this point");
2324+
2325+
FlutterMethodCall* setClientCall =
2326+
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
2327+
arguments:@[ @(123), self.mutableTemplateCopy ]];
2328+
[myInputPlugin handleMethodCall:setClientCall
2329+
result:^(id _Nullable result){
2330+
}];
2331+
2332+
FlutterTextInputView* mockInputView = OCMPartialMock(myInputPlugin.activeView);
2333+
OCMStub([mockInputView isScribbleAvailable]).andReturn(NO);
2334+
2335+
// yOffset = 200.
2336+
NSArray* yOffsetMatrix = @[ @1, @0, @0, @0, @0, @1, @0, @0, @0, @0, @1, @0, @0, @200, @0, @1 ];
2337+
2338+
FlutterMethodCall* setPlatformViewClientCall =
2339+
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditableSizeAndTransform"
2340+
arguments:@{@"transform" : yOffsetMatrix}];
2341+
[myInputPlugin handleMethodCall:setPlatformViewClientCall
2342+
result:^(id _Nullable result){
2343+
}];
2344+
2345+
CGRect offScreenRect = CGRectMake(0, -screen.bounds.size.height, 0, 0);
2346+
XCTAssert(CGRectEqualToRect(myInputPlugin.inputHider.frame, offScreenRect),
2347+
@"The input hider should stay offScreen if scribble is disabled.");
2348+
}
2349+
2350+
- (void)testInputHiderIsOnScreenWhenScribbleIsEnabled {
2351+
FlutterTextInputPlugin* myInputPlugin =
2352+
[[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
2353+
2354+
FlutterMethodCall* setClientCall =
2355+
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
2356+
arguments:@[ @(123), self.mutableTemplateCopy ]];
2357+
[myInputPlugin handleMethodCall:setClientCall
2358+
result:^(id _Nullable result){
2359+
}];
2360+
2361+
FlutterTextInputView* mockInputView = OCMPartialMock(myInputPlugin.activeView);
2362+
OCMStub([mockInputView isScribbleAvailable]).andReturn(YES);
2363+
2364+
// yOffset = 200.
2365+
NSArray* yOffsetMatrix = @[ @1, @0, @0, @0, @0, @1, @0, @0, @0, @0, @1, @0, @0, @200, @0, @1 ];
2366+
2367+
FlutterMethodCall* setPlatformViewClientCall =
2368+
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditableSizeAndTransform"
2369+
arguments:@{@"transform" : yOffsetMatrix}];
2370+
[myInputPlugin handleMethodCall:setPlatformViewClientCall
2371+
result:^(id _Nullable result){
2372+
}];
2373+
2374+
XCTAssertEqual(myInputPlugin.inputHider.frame.origin.y, 200,
2375+
@"The input hider should be brought on screen if scribble is enabled");
2376+
}
2377+
22982378
#pragma mark - Accessibility - Tests
22992379

23002380
- (void)testUITextInputAccessibilityNotHiddenWhenShowed {

0 commit comments

Comments
 (0)