mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Engine][iOS] Cancel animation when recieved UIKeyboardWillHideNotification with duration 0.0 (#164884)
fix https://github.com/flutter/flutter/issues/112281 The event log output shows that `UIKeyboardWillHideNotification` occurs immediately after `UIKeyboardWillShowNotification`. However, the animation is not cancelled in response to `UIKeyboardWillHideNotification`. This PR adds animation cancellation processing in response to `UIKeyboardWillHideNotification` with duration 0.0. https://github.com/user-attachments/assets/df0dbc6a-504b-476e-97ce-30e7ff40835f test app: https://github.com/koji-1009/pm_behavior_test ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Victoria Ashworth <15619084+vashworth@users.noreply.github.com>
This commit is contained in:
parent
8e3fee85a1
commit
02bdfd2a6c
@ -1523,6 +1523,17 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
|
||||
FlutterKeyboardMode keyboardMode = [self calculateKeyboardAttachMode:notification];
|
||||
CGFloat calculatedInset = [self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
|
||||
NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
|
||||
|
||||
// If the software keyboard is displayed before displaying the PasswordManager prompt,
|
||||
// UIKeyboardWillHideNotification will occur immediately after UIKeyboardWillShowNotification.
|
||||
// The duration of the animation will be 0.0, and the calculated inset will be 0.0.
|
||||
// In this case, it is necessary to cancel the animation and hide the keyboard immediately.
|
||||
// https://github.com/flutter/flutter/pull/164884
|
||||
if (keyboardMode == FlutterKeyboardModeHidden && calculatedInset == 0.0 && duration == 0.0) {
|
||||
[self hideKeyboardImmediately];
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid double triggering startKeyBoardAnimation.
|
||||
if (self.targetViewInsetBottom == calculatedInset) {
|
||||
@ -1530,7 +1541,6 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
}
|
||||
|
||||
self.targetViewInsetBottom = calculatedInset;
|
||||
NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
|
||||
|
||||
// Flag for simultaneous compounding animation calls.
|
||||
// This captures animation calls made while the keyboard animation is currently animating. If the
|
||||
@ -1773,6 +1783,21 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)hideKeyboardImmediately {
|
||||
[self invalidateKeyboardAnimationVSyncClient];
|
||||
if (self.keyboardAnimationView) {
|
||||
[self.keyboardAnimationView.layer removeAllAnimations];
|
||||
[self removeKeyboardAnimationView];
|
||||
self.keyboardAnimationView = nil;
|
||||
}
|
||||
if (self.keyboardSpringAnimation) {
|
||||
self.keyboardSpringAnimation = nil;
|
||||
}
|
||||
// Reset targetViewInsetBottom to 0.0.
|
||||
self.targetViewInsetBottom = 0.0;
|
||||
[self ensureViewportMetricsIsCorrect];
|
||||
}
|
||||
|
||||
- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
|
||||
// If keyboard animation is null or not a spring animation, fallback to DisplayLink tracking.
|
||||
if (keyboardAnimation == nil || ![keyboardAnimation isKindOfClass:[CASpringAnimation class]]) {
|
||||
|
||||
@ -142,6 +142,7 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
|
||||
- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
|
||||
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
|
||||
- (void)hideKeyboardImmediately;
|
||||
- (UIView*)keyboardAnimationView;
|
||||
- (SpringAnimation*)keyboardSpringAnimation;
|
||||
- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
|
||||
@ -779,6 +780,57 @@ extern NSNotificationName const FlutterViewControllerWillDealloc;
|
||||
XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
|
||||
}
|
||||
|
||||
- (void)testStopKeyBoardAnimationWhenReceivedWillHideNotificationAfterWillShowNotification {
|
||||
// see: https://github.com/flutter/flutter/issues/112281
|
||||
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] init];
|
||||
[engine runWithEntrypoint:nil];
|
||||
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
|
||||
nibName:nil
|
||||
bundle:nil];
|
||||
FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
|
||||
UIScreen* screen = [self setUpMockScreen];
|
||||
CGRect viewFrame = screen.bounds;
|
||||
[self setUpMockView:viewControllerMock
|
||||
screen:screen
|
||||
viewFrame:viewFrame
|
||||
convertedFrame:viewFrame];
|
||||
viewControllerMock.targetViewInsetBottom = 0;
|
||||
|
||||
CGFloat screenHeight = screen.bounds.size.height;
|
||||
CGFloat screenWidth = screen.bounds.size.height;
|
||||
CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
|
||||
BOOL isLocal = YES;
|
||||
|
||||
// Receive will show notification
|
||||
NSNotification* fakeShowNotification =
|
||||
[NSNotification notificationWithName:UIKeyboardWillShowNotification
|
||||
object:nil
|
||||
userInfo:@{
|
||||
@"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
|
||||
@"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
|
||||
@"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
|
||||
}];
|
||||
[viewControllerMock handleKeyboardNotification:fakeShowNotification];
|
||||
XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
|
||||
|
||||
// Receive will hide notification
|
||||
NSNotification* fakeHideNotification =
|
||||
[NSNotification notificationWithName:UIKeyboardWillHideNotification
|
||||
object:nil
|
||||
userInfo:@{
|
||||
@"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
|
||||
@"UIKeyboardAnimationDurationUserInfoKey" : @(0.0),
|
||||
@"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
|
||||
}];
|
||||
[viewControllerMock handleKeyboardNotification:fakeHideNotification];
|
||||
XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
|
||||
|
||||
// Check if the keyboard animation is stopped.
|
||||
XCTAssertNil(viewControllerMock.keyboardAnimationView);
|
||||
XCTAssertNil(viewControllerMock.keyboardSpringAnimation);
|
||||
}
|
||||
|
||||
- (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
|
||||
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
|
||||
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user