diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm index c263b37f961d4..a4fb7a04d82f4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm @@ -21,6 +21,10 @@ // it is activated. static constexpr double kUITextInputAccessibilityEnablingDelaySeconds = 0.5; +// A delay before reenabling the UIView areAnimationsEnabled to YES +// in order for becomeFirstResponder to receive the proper value +static const NSTimeInterval kKeyboardAnimationDelaySeconds = 0.1; + // The "canonical" invalid CGRect, similar to CGRectNull, used to // indicate a CGRect involved in firstRectForRange calculation is // invalid. The specific value is chosen so that if firstRectForRange @@ -2369,8 +2373,13 @@ - (void)dismissKeyboardScreenshot { - (void)showKeyboardAndRemoveScreenshot { [UIView setAnimationsEnabled:NO]; [_cachedFirstResponder becomeFirstResponder]; - [UIView setAnimationsEnabled:YES]; - [self dismissKeyboardScreenshot]; + // UIKit does not immediately access the areAnimationsEnabled Boolean so a delay is needed before + // returned + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, kKeyboardAnimationDelaySeconds * NSEC_PER_SEC), + dispatch_get_main_queue(), ^{ + [UIView setAnimationsEnabled:YES]; + [self dismissKeyboardScreenshot]; + }); } - (void)handlePointerMove:(CGFloat)pointerY { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index d34101397ec61..edc95d8b3b4be 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -75,6 +75,8 @@ - (void)cleanUpViewHierarchy:(BOOL)includeActiveView - (UIView*)hostView; - (void)addToInputParentViewIfNeeded:(FlutterTextInputView*)inputView; - (void)startLiveTextInput; +- (void)showKeyboardAndRemoveScreenshot; + @end @interface FlutterTextInputPluginTest : XCTestCase @@ -2890,5 +2892,25 @@ - (void)testInteractiveKeyboardKeyboardAnimatesToDismissalPositionalOnPointerUp }]; textInputPlugin.cachedFirstResponder = nil; } +- (void)testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsNotImmediatelyEnable { + [UIView setAnimationsEnabled:YES]; + [textInputPlugin showKeyboardAndRemoveScreenshot]; + XCTAssertFalse( + UIView.areAnimationsEnabled, + @"The animation should still be disabled following showKeyboardAndRemoveScreenshot"); +} + +- (void)testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsReenabledAfterDelay { + [UIView setAnimationsEnabled:YES]; + [textInputPlugin showKeyboardAndRemoveScreenshot]; + + NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) { + // This will be enabled after a delay + return UIView.areAnimationsEnabled; + }]; + XCTNSPredicateExpectation* expectation = + [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil]; + [self waitForExpectations:@[ expectation ] timeout:10.0]; +} @end