@@ -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;
@@ -422,6 +423,72 @@ - (void)testAutocorrectionPromptRectDoesNotAppearDuringScribble {
422423 }
423424}
424425
426+ - (void )testInputHiderOverlapWithTextWhenScribbleIsDisabledAfterIOS17AndDoesNotOverlapBeforeIOS17 {
427+ FlutterTextInputPlugin* myInputPlugin =
428+ [[FlutterTextInputPlugin alloc ] initWithDelegate: OCMClassMock ([FlutterEngine class ])];
429+
430+ FlutterMethodCall* setClientCall =
431+ [FlutterMethodCall methodCallWithMethodName: @" TextInput.setClient"
432+ arguments: @[ @(123 ), self .mutableTemplateCopy ]];
433+ [myInputPlugin handleMethodCall: setClientCall
434+ result: ^(id _Nullable result){
435+ }];
436+
437+ FlutterTextInputView* mockInputView = OCMPartialMock (myInputPlugin.activeView );
438+ OCMStub ([mockInputView isScribbleAvailable ]).andReturn (NO );
439+
440+ // yOffset = 200.
441+ NSArray * yOffsetMatrix = @[ @1 , @0 , @0 , @0 , @0 , @1 , @0 , @0 , @0 , @0 , @1 , @0 , @0 , @200 , @0 , @1 ];
442+
443+ FlutterMethodCall* setPlatformViewClientCall =
444+ [FlutterMethodCall methodCallWithMethodName: @" TextInput.setEditableSizeAndTransform"
445+ arguments: @{@" transform" : yOffsetMatrix}];
446+ [myInputPlugin handleMethodCall: setPlatformViewClientCall
447+ result: ^(id _Nullable result){
448+ }];
449+
450+ if (@available (iOS 17 , *)) {
451+ XCTAssert (CGRectEqualToRect (myInputPlugin.inputHider .frame , CGRectMake (0 , 200 , 0 , 0 )),
452+ @" The input hider should overlap with the text on and after iOS 17" );
453+
454+ } else {
455+ XCTAssert (CGRectEqualToRect (myInputPlugin.inputHider .frame , CGRectZero),
456+ @" The input hider should be on the origin of screen on and before iOS 16." );
457+ }
458+ }
459+
460+ - (void )testSetSelectionRectsNotifiesTextChangeAfterIOS17AndDoesNotNotifyBeforeIOS17 {
461+ FlutterTextInputPlugin* myInputPlugin =
462+ [[FlutterTextInputPlugin alloc ] initWithDelegate: OCMClassMock ([FlutterEngine class ])];
463+
464+ FlutterMethodCall* setClientCall =
465+ [FlutterMethodCall methodCallWithMethodName: @" TextInput.setClient"
466+ arguments: @[ @(123 ), self .mutableTemplateCopy ]];
467+ [myInputPlugin handleMethodCall: setClientCall
468+ result: ^(id _Nullable result){
469+ }];
470+
471+ id mockInputDelegate = OCMProtocolMock (@protocol (UITextInputDelegate));
472+ myInputPlugin.activeView .inputDelegate = mockInputDelegate;
473+
474+ NSArray <NSNumber *>* selectionRect = [NSArray arrayWithObjects: @0 , @0 , @100 , @100 , @0 , @1 , nil ];
475+ NSArray * selectionRects = [NSArray arrayWithObjects: selectionRect, nil ];
476+ FlutterMethodCall* methodCall =
477+ [FlutterMethodCall methodCallWithMethodName: @" Scribble.setSelectionRects"
478+ arguments: selectionRects];
479+ [myInputPlugin handleMethodCall: methodCall
480+ result: ^(id _Nullable result){
481+ }];
482+
483+ if (@available (iOS 17.0 , *)) {
484+ OCMVerify ([mockInputDelegate textWillChange: myInputPlugin.activeView]);
485+ OCMVerify ([mockInputDelegate textDidChange: myInputPlugin.activeView]);
486+ } else {
487+ OCMVerify (never (), [mockInputDelegate textWillChange: myInputPlugin.activeView]);
488+ OCMVerify (never (), [mockInputDelegate textDidChange: myInputPlugin.activeView]);
489+ }
490+ }
491+
425492- (void )testTextRangeFromPositionMatchesUITextViewBehavior {
426493 FlutterTextInputView* inputView = [[FlutterTextInputView alloc ] initWithOwner: textInputPlugin];
427494 FlutterTextPosition* fromPosition = [FlutterTextPosition positionWithIndex: 2 ];
0 commit comments