@@ -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