From 7b13f64c89286c10ba570d2b9858531afd44fed0 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Thu, 25 May 2023 12:56:19 +0800 Subject: [PATCH 01/23] [iOS][Keyboard] Wait vsync on UI thread and update viewport inset to avoid jitter. --- .../framework/Source/FlutterViewController.mm | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 1831b4d2f546b..b554c116d5909 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1640,8 +1640,8 @@ - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation { } - (void)setupKeyboardAnimationVsyncClient { - auto callback = [weakSelf = - [self getWeakPtr]](std::unique_ptr recorder) { + auto keyboardAnimationCallback = [weakSelf = [self getWeakPtr]]( + fml::TimePoint keyboardAnimationTargetTime) { if (!weakSelf) { return; } @@ -1664,9 +1664,8 @@ - (void)setupKeyboardAnimationVsyncClient { [flutterViewController updateViewportMetricsIfNeeded]; } } else { - fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() - - flutterViewController.get().keyboardAnimationStartTime; - + fml::TimeDelta timeElapsed = + keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime; flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; [flutterViewController updateViewportMetricsIfNeeded]; @@ -1675,9 +1674,22 @@ - (void)setupKeyboardAnimationVsyncClient { flutter::Shell& shell = [_engine.get() shell]; NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); + + // Need to call the updateViewportMetrics signal after flutter UI thread's process callback, + // So here need to wait vsync on UI thread instead of posting the signal + // on platform thread directly. + auto uiCallback = [keyboardAnimationCallback, + &shell](std::unique_ptr recorder) { + fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); + fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; + shell.GetTaskRunners().GetPlatformTaskRunner()->PostTask( + [keyboardAnimationCallback, keyboardAnimationTargetTime] { + keyboardAnimationCallback(keyboardAnimationTargetTime); + }); + }; _keyboardAnimationVSyncClient = - [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner() - callback:callback]; + [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetUITaskRunner() + callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; [_keyboardAnimationVSyncClient await]; } From 4be1ecc01d779cd08f821be3c8384d47c169931e Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Thu, 25 May 2023 15:36:04 +0800 Subject: [PATCH 02/23] ++ --- .../darwin/ios/framework/Source/FlutterViewController.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index b554c116d5909..dca176a4d1c9f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1676,7 +1676,7 @@ - (void)setupKeyboardAnimationVsyncClient { @"_keyboardAnimationVSyncClient must be nil when setup"); // Need to call the updateViewportMetrics signal after flutter UI thread's process callback, - // So here need to wait vsync on UI thread instead of posting the signal + // so here need to wait vsync on UI thread instead of posting the signal // on platform thread directly. auto uiCallback = [keyboardAnimationCallback, &shell](std::unique_ptr recorder) { From cba3f18bab6fc3eedaed4ccc68d984c3e3659325 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 30 May 2023 14:10:39 +0800 Subject: [PATCH 03/23] ++ --- .../framework/Source/FlutterViewController.mm | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index dca176a4d1c9f..26f47f423e31b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -44,6 +44,8 @@ NSNotificationName const FlutterViewControllerShowHomeIndicator = @"FlutterViewControllerShowHomeIndicator"; +typedef std::function KeyboardAnimationCallback; + // Struct holding data to help adapt system mouse/trackpad events to embedder events. typedef struct MouseState { // Current coordinate of the mouse cursor in physical device pixels. @@ -1596,7 +1598,41 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; - [self setupKeyboardAnimationVsyncClient]; + + KeyboardAnimationCallback keyboardAnimationCallback = + [weakSelf = [self getWeakPtr]](fml::TimePoint keyboardAnimationTargetTime) { + if (!weakSelf) { + return; + } + fml::scoped_nsobject flutterViewController( + [(FlutterViewController*)weakSelf.get() retain]); + if (!flutterViewController) { + return; + } + + if ([flutterViewController keyboardAnimationView].superview == nil) { + // Ensure the keyboardAnimationView is in view hierarchy when animation running. + [flutterViewController.get().view + addSubview:[flutterViewController keyboardAnimationView]]; + } + + if ([flutterViewController keyboardSpringAnimation] == nil) { + if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + flutterViewController.get() + .keyboardAnimationView.layer.presentationLayer.frame.origin.y; + [flutterViewController updateViewportMetricsIfNeeded]; + } + } else { + fml::TimeDelta timeElapsed = + keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + [[flutterViewController keyboardSpringAnimation] + curveFunction:timeElapsed.ToSecondsF()]; + [flutterViewController updateViewportMetricsIfNeeded]; + } + }; + [self setupKeyboardAnimationVsyncClient:keyboardAnimationCallback]; VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; [UIView animateWithDuration:duration @@ -1639,38 +1675,7 @@ - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation { toValue:self.targetViewInsetBottom]); } -- (void)setupKeyboardAnimationVsyncClient { - auto keyboardAnimationCallback = [weakSelf = [self getWeakPtr]]( - fml::TimePoint keyboardAnimationTargetTime) { - if (!weakSelf) { - return; - } - fml::scoped_nsobject flutterViewController( - [(FlutterViewController*)weakSelf.get() retain]); - if (!flutterViewController) { - return; - } - - if ([flutterViewController keyboardAnimationView].superview == nil) { - // Ensure the keyboardAnimationView is in view hierarchy when animation running. - [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; - } - - if ([flutterViewController keyboardSpringAnimation] == nil) { - if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - flutterViewController.get() - .keyboardAnimationView.layer.presentationLayer.frame.origin.y; - [flutterViewController updateViewportMetricsIfNeeded]; - } - } else { - fml::TimeDelta timeElapsed = - keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime; - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; - [flutterViewController updateViewportMetricsIfNeeded]; - } - }; +- (void)setupKeyboardAnimationVsyncClient:(KeyboardAnimationCallback)keyboardAnimationCallback { flutter::Shell& shell = [_engine.get() shell]; NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); From 832bd128d39f4b63a802cdafb04e4a019cb65c78 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 30 May 2023 15:13:00 +0800 Subject: [PATCH 04/23] ++ --- .../framework/Source/FlutterViewController.mm | 10 ++++++---- .../Source/FlutterViewControllerTest.mm | 13 ------------- .../Source/FlutterViewControllerTest_mrc.mm | 19 +++++++++++++++++-- .../Source/FlutterViewController_Internal.h | 2 ++ 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 26f47f423e31b..bddaa985fcb23 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -44,8 +44,6 @@ NSNotificationName const FlutterViewControllerShowHomeIndicator = @"FlutterViewControllerShowHomeIndicator"; -typedef std::function KeyboardAnimationCallback; - // Struct holding data to help adapt system mouse/trackpad events to embedder events. typedef struct MouseState { // Current coordinate of the mouse cursor in physical device pixels. @@ -1599,7 +1597,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; - KeyboardAnimationCallback keyboardAnimationCallback = + FlutterKeyboardAnimationCallback keyboardAnimationCallback = [weakSelf = [self getWeakPtr]](fml::TimePoint keyboardAnimationTargetTime) { if (!weakSelf) { return; @@ -1675,7 +1673,11 @@ - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation { toValue:self.targetViewInsetBottom]); } -- (void)setupKeyboardAnimationVsyncClient:(KeyboardAnimationCallback)keyboardAnimationCallback { +- (void)setupKeyboardAnimationVsyncClient: + (FlutterKeyboardAnimationCallback)keyboardAnimationCallback { + if (!keyboardAnimationCallback) { + return; + } flutter::Shell& shell = [_engine.get() shell]; NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index b02109c236672..99149064c1b3a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -135,7 +135,6 @@ - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification; - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification; - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame; - (void)startKeyBoardAnimation:(NSTimeInterval)duration; -- (void)setupKeyboardAnimationVsyncClient; - (UIView*)keyboardAnimationView; - (SpringAnimation*)keyboardSpringAnimation; - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation; @@ -197,18 +196,6 @@ - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient { OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]); } -- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient { - FlutterEngine* engine = [[FlutterEngine alloc] init]; - [engine runWithEntrypoint:nil]; - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine - nibName:nil - bundle:nil]; - FlutterViewController* viewControllerMock = OCMPartialMock(viewController); - viewControllerMock.targetViewInsetBottom = 100; - [viewControllerMock startKeyBoardAnimation:0.25]; - OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]); -} - - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm index e18b0b55c9335..9646d836a0c13 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm @@ -7,6 +7,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" FLUTTER_ASSERT_NOT_ARC @@ -31,7 +32,9 @@ @interface FlutterViewController (Testing) @property(nonatomic, retain) VSyncClient* touchRateCorrectionVSyncClient; - (void)createTouchRateCorrectionVSyncClientIfNeeded; -- (void)setupKeyboardAnimationVsyncClient; +- (void)setupKeyboardAnimationVsyncClient: + (FlutterKeyboardAnimationCallback)keyboardAnimationCallback; +- (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches; @end @@ -53,7 +56,8 @@ - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterV FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; - [viewController setupKeyboardAnimationVsyncClient]; + FlutterKeyboardAnimationCallback callback = [](fml::TimePoint targetTime) {}; + [viewController setupKeyboardAnimationVsyncClient:callback]; XCTAssertNotNil(viewController.keyboardAnimationVSyncClient); CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink]; XCTAssertNotNil(link); @@ -173,4 +177,15 @@ - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly { XCTAssertFalse(link.isPaused); } +- (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + viewController.targetViewInsetBottom = 100; + [viewController startKeyBoardAnimation:0.25]; + XCTAssertNotNil(viewController.keyboardAnimationVSyncClient); +} + @end diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index 664b4c282b9f8..83e5ff1a6b358 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -33,6 +33,8 @@ typedef NS_ENUM(NSInteger, FlutterKeyboardMode) { FlutterKeyboardModeFloating = 2, }; +typedef std::function FlutterKeyboardAnimationCallback; + @interface FlutterViewController () @property(class, nonatomic, readonly) BOOL accessibilityIsOnOffSwitchLabelsEnabled; From 5fd112d46c4c84fbc7212e07529eb69c776085ea Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 30 May 2023 15:18:15 +0800 Subject: [PATCH 05/23] ++ --- .../framework/Source/FlutterViewControllerTest_mrc.mm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm index 9646d836a0c13..aa8ce406a50c8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm @@ -188,4 +188,15 @@ - (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorr XCTAssertNotNil(viewController.keyboardAnimationVSyncClient); } +- (void) + testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + [viewController setupKeyboardAnimationVsyncClient:nil]; + XCTAssertNil(viewController.keyboardAnimationVSyncClient); +} + @end From e26033dae8f3113af6ac32988f57d5015e385e2b Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 30 May 2023 15:46:27 +0800 Subject: [PATCH 06/23] ++ --- .../Source/FlutterViewControllerTest.mm | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 99149064c1b3a..0a53c415c431a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -9,6 +9,7 @@ #import "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/pointer_data.h" #import "flutter/lib/ui/window/viewport_metrics.h" +#import "flutter/shell/common/shell.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" @@ -26,6 +27,7 @@ - (FlutterTextInputPlugin*)textInputPlugin; - (void)sendKeyEvent:(const FlutterKeyEvent&)event callback:(nullable FlutterKeyEventCallback)callback userData:(nullable void*)userData; +- (flutter::Shell&)shell; @end /// Sometimes we have to use a custom mock to avoid retain cycles in OCMock. @@ -138,6 +140,8 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration; - (UIView*)keyboardAnimationView; - (SpringAnimation*)keyboardSpringAnimation; - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation; +- (void)setupKeyboardAnimationVsyncClient: + (FlutterKeyboardAnimationCallback)keyboardAnimationCallback; - (void)ensureViewportMetricsIsCorrect; - (void)invalidateKeyboardAnimationVSyncClient; - (void)addInternalPlugins; @@ -212,6 +216,29 @@ - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded OCMVerify([viewControllerMock setupKeyboardSpringAnimationIfNeeded:keyboardAnimation]); } +- (void)testKeyboardAnimationWillWaitUIThreadVsync { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + // Post a task to UI thread to block the thread. + flutter::Shell& shell = [engine shell]; + const int delayTime = 1; + shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); + XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; + CFTimeInterval startTime = CACurrentMediaTime(); + CFTimeInterval fulfillTime; + FlutterKeyboardAnimationCallback callback = [&expectation, + &fulfillTime](fml::TimePoint targetTime) { + fulfillTime = CACurrentMediaTime(); + [expectation fulfill]; + }; + [viewController setupKeyboardAnimationVsyncClient:callback]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + XCTAssertTrue(fulfillTime - startTime > delayTime); +} + - (void)testSetupKeyboardSpringAnimationIfNeeded { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; From 057b54ea40acca7d983b112fb1248a2e55210209 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 30 May 2023 15:59:34 +0800 Subject: [PATCH 07/23] ++ --- .../Source/FlutterViewControllerTest.mm | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 0a53c415c431a..16cef9fe13d08 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -188,6 +188,29 @@ - (id)setupMockMainScreenAndView:(FlutterViewController*)viewControllerMock return mockView; } +- (void)testKeyboardAnimationWillWaitUIThreadVsync { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + // Post a task to UI thread to block the thread. + flutter::Shell& shell = [engine shell]; + const int delayTime = 1; + shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); + XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; + CFTimeInterval startTime = CACurrentMediaTime(); + CFTimeInterval fulfillTime; + FlutterKeyboardAnimationCallback callback = [&expectation, + &fulfillTime](fml::TimePoint targetTime) { + fulfillTime = CACurrentMediaTime(); + [expectation fulfill]; + }; + [viewController setupKeyboardAnimationVsyncClient:callback]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + XCTAssertTrue(fulfillTime - startTime > delayTime); +} + - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; @@ -216,29 +239,6 @@ - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded OCMVerify([viewControllerMock setupKeyboardSpringAnimationIfNeeded:keyboardAnimation]); } -- (void)testKeyboardAnimationWillWaitUIThreadVsync { - FlutterEngine* engine = [[FlutterEngine alloc] init]; - [engine runWithEntrypoint:nil]; - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine - nibName:nil - bundle:nil]; - // Post a task to UI thread to block the thread. - flutter::Shell& shell = [engine shell]; - const int delayTime = 1; - shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); - XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; - CFTimeInterval startTime = CACurrentMediaTime(); - CFTimeInterval fulfillTime; - FlutterKeyboardAnimationCallback callback = [&expectation, - &fulfillTime](fml::TimePoint targetTime) { - fulfillTime = CACurrentMediaTime(); - [expectation fulfill]; - }; - [viewController setupKeyboardAnimationVsyncClient:callback]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - XCTAssertTrue(fulfillTime - startTime > delayTime); -} - - (void)testSetupKeyboardSpringAnimationIfNeeded { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; From bfa816d44ef792081ca5db32ef2499a4930c17f9 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 30 May 2023 16:00:48 +0800 Subject: [PATCH 08/23] ++ --- .../Source/FlutterViewControllerTest.mm | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 16cef9fe13d08..db06e4a239b17 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -188,29 +188,6 @@ - (id)setupMockMainScreenAndView:(FlutterViewController*)viewControllerMock return mockView; } -- (void)testKeyboardAnimationWillWaitUIThreadVsync { - FlutterEngine* engine = [[FlutterEngine alloc] init]; - [engine runWithEntrypoint:nil]; - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine - nibName:nil - bundle:nil]; - // Post a task to UI thread to block the thread. - flutter::Shell& shell = [engine shell]; - const int delayTime = 1; - shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); - XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; - CFTimeInterval startTime = CACurrentMediaTime(); - CFTimeInterval fulfillTime; - FlutterKeyboardAnimationCallback callback = [&expectation, - &fulfillTime](fml::TimePoint targetTime) { - fulfillTime = CACurrentMediaTime(); - [expectation fulfill]; - }; - [viewController setupKeyboardAnimationVsyncClient:callback]; - [self waitForExpectationsWithTimeout:5.0 handler:nil]; - XCTAssertTrue(fulfillTime - startTime > delayTime); -} - - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient { FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; @@ -465,6 +442,29 @@ - (void)testShouldIgnoreKeyboardNotification { } } +- (void)testKeyboardAnimationWillWaitUIThreadVsync { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + // Post a task to UI thread to block the thread. + flutter::Shell& shell = [engine shell]; + const int delayTime = 1; + shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); + XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; + CFTimeInterval startTime = CACurrentMediaTime(); + CFTimeInterval fulfillTime; + FlutterKeyboardAnimationCallback callback = [&expectation, + &fulfillTime](fml::TimePoint targetTime) { + fulfillTime = CACurrentMediaTime(); + [expectation fulfill]; + }; + [viewController setupKeyboardAnimationVsyncClient:callback]; + [self waitForExpectationsWithTimeout:5.0 handler:nil]; + XCTAssertTrue(fulfillTime - startTime > delayTime); +} + - (void)testCalculateKeyboardAttachMode { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; From 8006b9522ad66a00a023e1b2f7f07d23bfcd0e62 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 11:12:46 +0800 Subject: [PATCH 09/23] nits --- .../framework/Source/FlutterViewController.mm | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index bddaa985fcb23..ead48aa99e779 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1608,9 +1608,19 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { return; } + // If the view controller's view is not loaded, bail out. + if (flutterViewController.get().viewIfLoaded == nil) { + return; + } + // If the view for tracking keyboard animation is nil, means it is not + // created, bail out. + if ([flutterViewController keyboardAnimationView] == nil) { + return; + } + if ([flutterViewController keyboardAnimationView].superview == nil) { // Ensure the keyboardAnimationView is in view hierarchy when animation running. - [flutterViewController.get().view + [flutterViewController.get().viewIfLoaded addSubview:[flutterViewController keyboardAnimationView]]; } @@ -1682,9 +1692,7 @@ - (void)setupKeyboardAnimationVsyncClient: NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); - // Need to call the updateViewportMetrics signal after flutter UI thread's process callback, - // so here need to wait vsync on UI thread instead of posting the signal - // on platform thread directly. + // Make sure the new viewport metrics get sent after the begin frame event has processed. auto uiCallback = [keyboardAnimationCallback, &shell](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); From 2dd4636f9ca4c6cdac888f98d3fc68c2a4cd3c6d Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 11:27:33 +0800 Subject: [PATCH 10/23] nits --- .../ios/framework/Source/FlutterViewControllerTest.mm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index db06e4a239b17..838dc4865a608 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -443,6 +443,12 @@ - (void)testShouldIgnoreKeyboardNotification { } - (void)testKeyboardAnimationWillWaitUIThreadVsync { + // We need to make sure the new viewport metrics get sent after the + // begin frame event has processed. And this test is to expect that the callback + // will sync with UI thread. So just simulate a lot of works on UI thread and + // test the keyboard animation callback will execute until UI task completed. + // Related issue: https://github.com/flutter/flutter/issues/120555. + FlutterEngine* engine = [[FlutterEngine alloc] init]; [engine runWithEntrypoint:nil]; FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine @@ -453,13 +459,13 @@ - (void)testKeyboardAnimationWillWaitUIThreadVsync { const int delayTime = 1; shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; - CFTimeInterval startTime = CACurrentMediaTime(); CFTimeInterval fulfillTime; FlutterKeyboardAnimationCallback callback = [&expectation, &fulfillTime](fml::TimePoint targetTime) { fulfillTime = CACurrentMediaTime(); [expectation fulfill]; }; + CFTimeInterval startTime = CACurrentMediaTime(); [viewController setupKeyboardAnimationVsyncClient:callback]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertTrue(fulfillTime - startTime > delayTime); From d5db10c563130d1d5a69c5ea923fd338fe56e7ef Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 13:33:37 +0800 Subject: [PATCH 11/23] nits --- .../ios/framework/Source/FlutterEngine.mm | 7 ++++++- .../framework/Source/FlutterEngine_Internal.h | 3 ++- .../framework/Source/FlutterViewController.mm | 18 ++++++++---------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 1bde46a6f9853..0633a78211607 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -333,7 +333,12 @@ - (void)dispatchPointerDataPacket:(std::unique_ptr)p return _shell->GetTaskRunners().GetPlatformTaskRunner(); } -- (fml::RefPtr)RasterTaskRunner { +- (fml::RefPtr)uiTaskRunner { + FML_DCHECK(_shell); + return _shell->GetTaskRunners().GetUITaskRunner(); +} + +- (fml::RefPtr)rasterTaskRunner { FML_DCHECK(_shell); return _shell->GetTaskRunners().GetRasterTaskRunner(); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 6fe25e406af6f..2d476e93ebc3c 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -39,7 +39,8 @@ extern NSString* const kFlutterEngineWillDealloc; - (void)dispatchPointerDataPacket:(std::unique_ptr)packet; - (fml::RefPtr)platformTaskRunner; -- (fml::RefPtr)RasterTaskRunner; +- (fml::RefPtr)uiTaskRunner; +- (fml::RefPtr)rasterTaskRunner; - (fml::WeakPtr)platformView; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index ead48aa99e779..8184705da878d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -574,8 +574,8 @@ - (void)installFirstFrameCallback { // Start on the platform thread. weakPlatformView->SetNextFrameCallback([weakSelf = [self getWeakPtr], platformTaskRunner = [_engine.get() platformTaskRunner], - RasterTaskRunner = [_engine.get() RasterTaskRunner]]() { - FML_DCHECK(RasterTaskRunner->RunsTasksOnCurrentThread()); + rasterTaskRunner = [_engine.get() rasterTaskRunner]]() { + FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread()); // Get callback on raster thread and jump back to platform thread. platformTaskRunner->PostTask([weakSelf]() { if (weakSelf) { @@ -1688,23 +1688,21 @@ - (void)setupKeyboardAnimationVsyncClient: if (!keyboardAnimationCallback) { return; } - flutter::Shell& shell = [_engine.get() shell]; NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); // Make sure the new viewport metrics get sent after the begin frame event has processed. auto uiCallback = [keyboardAnimationCallback, - &shell](std::unique_ptr recorder) { + platformTaskRunner = [_engine.get() platformTaskRunner]]( + std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; - shell.GetTaskRunners().GetPlatformTaskRunner()->PostTask( - [keyboardAnimationCallback, keyboardAnimationTargetTime] { - keyboardAnimationCallback(keyboardAnimationTargetTime); - }); + platformTaskRunner->PostTask([keyboardAnimationCallback, keyboardAnimationTargetTime] { + keyboardAnimationCallback(keyboardAnimationTargetTime); + }); }; _keyboardAnimationVSyncClient = - [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetUITaskRunner() - callback:uiCallback]; + [[VSyncClient alloc] initWithTaskRunner:[_engine.get() uiTaskRunner] callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; [_keyboardAnimationVSyncClient await]; } From 3de6dd5f668e485a6f8498309ff0b1a0b8ba6d20 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 16:53:17 +0800 Subject: [PATCH 12/23] ++ --- .../ios/framework/Source/FlutterViewControllerTest.mm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 838dc4865a608..9532d8a692ff4 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -9,7 +9,6 @@ #import "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/pointer_data.h" #import "flutter/lib/ui/window/viewport_metrics.h" -#import "flutter/shell/common/shell.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" @@ -27,7 +26,7 @@ - (FlutterTextInputPlugin*)textInputPlugin; - (void)sendKeyEvent:(const FlutterKeyEvent&)event callback:(nullable FlutterKeyEventCallback)callback userData:(nullable void*)userData; -- (flutter::Shell&)shell; +- (fml::RefPtr)uiTaskRunner; @end /// Sometimes we have to use a custom mock to avoid retain cycles in OCMock. @@ -455,9 +454,8 @@ - (void)testKeyboardAnimationWillWaitUIThreadVsync { nibName:nil bundle:nil]; // Post a task to UI thread to block the thread. - flutter::Shell& shell = [engine shell]; const int delayTime = 1; - shell.GetTaskRunners().GetUITaskRunner()->PostTask([] { sleep(delayTime); }); + [engine uiTaskRunner]->PostTask([] { sleep(delayTime); }); XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; CFTimeInterval fulfillTime; FlutterKeyboardAnimationCallback callback = [&expectation, From 4e57fd58a754665b6d4d03336f35f20c03e164ed Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 17:17:41 +0800 Subject: [PATCH 13/23] ++ --- .../darwin/ios/framework/Source/FlutterViewControllerTest.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 9532d8a692ff4..afae0dea49027 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -26,7 +26,7 @@ - (FlutterTextInputPlugin*)textInputPlugin; - (void)sendKeyEvent:(const FlutterKeyEvent&)event callback:(nullable FlutterKeyEventCallback)callback userData:(nullable void*)userData; -- (fml::RefPtr)uiTaskRunner; +- (fml::RefPtr)uiTaskRunner; @end /// Sometimes we have to use a custom mock to avoid retain cycles in OCMock. From 53ea733610ffcfc4be65a61030a7187805afd1f8 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 18:09:48 +0800 Subject: [PATCH 14/23] ++ --- .../ios/framework/Source/FlutterViewController.mm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 8184705da878d..b6f4ab529cb46 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1693,13 +1693,13 @@ - (void)setupKeyboardAnimationVsyncClient: // Make sure the new viewport metrics get sent after the begin frame event has processed. auto uiCallback = [keyboardAnimationCallback, - platformTaskRunner = [_engine.get() platformTaskRunner]]( - std::unique_ptr recorder) { + engine = _engine](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; - platformTaskRunner->PostTask([keyboardAnimationCallback, keyboardAnimationTargetTime] { - keyboardAnimationCallback(keyboardAnimationTargetTime); - }); + [engine.get() platformTaskRunner]->PostTask( + [keyboardAnimationCallback, keyboardAnimationTargetTime] { + keyboardAnimationCallback(keyboardAnimationTargetTime); + }); }; _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine.get() uiTaskRunner] callback:uiCallback]; From 8406fe90fa87d17ec1e59f126c45b46f6a750010 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Fri, 9 Jun 2023 18:20:44 +0800 Subject: [PATCH 15/23] ++ --- .../darwin/ios/framework/Source/FlutterViewController.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index b6f4ab529cb46..c61ed30b30994 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1617,6 +1617,11 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { if ([flutterViewController keyboardAnimationView] == nil) { return; } + // If keyboardAnimationVSyncClient is nil, means the animation ends. + // And should bail out. + if (flutterViewController.get().keyboardAnimationVSyncClient == nil) { + return; + } if ([flutterViewController keyboardAnimationView].superview == nil) { // Ensure the keyboardAnimationView is in view hierarchy when animation running. From ffade70dcf40b9cfde9463845ae5bc70bc86292f Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Sat, 10 Jun 2023 10:04:55 +0800 Subject: [PATCH 16/23] Switch to objc block --- .../framework/Source/FlutterViewController.mm | 101 +++++++++--------- .../Source/FlutterViewController_Internal.h | 2 +- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index c61ed30b30994..f6767db02fdaa 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1597,54 +1597,54 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { // Invalidate old vsync client if old animation is not completed. [self invalidateKeyboardAnimationVSyncClient]; - FlutterKeyboardAnimationCallback keyboardAnimationCallback = - [weakSelf = [self getWeakPtr]](fml::TimePoint keyboardAnimationTargetTime) { - if (!weakSelf) { - return; - } - fml::scoped_nsobject flutterViewController( - [(FlutterViewController*)weakSelf.get() retain]); - if (!flutterViewController) { - return; - } + fml::WeakPtr weakSelf = [self getWeakPtr]; + FlutterKeyboardAnimationCallback keyboardAnimationCallback = ^( + fml::TimePoint keyboardAnimationTargetTime) { + if (!weakSelf) { + return; + } + fml::scoped_nsobject flutterViewController( + [(FlutterViewController*)weakSelf.get() retain]); + if (!flutterViewController) { + return; + } - // If the view controller's view is not loaded, bail out. - if (flutterViewController.get().viewIfLoaded == nil) { - return; - } - // If the view for tracking keyboard animation is nil, means it is not - // created, bail out. - if ([flutterViewController keyboardAnimationView] == nil) { - return; - } - // If keyboardAnimationVSyncClient is nil, means the animation ends. - // And should bail out. - if (flutterViewController.get().keyboardAnimationVSyncClient == nil) { - return; - } + // If the view controller's view is not loaded, bail out. + if (flutterViewController.get().viewIfLoaded == nil) { + return; + } + // If the view for tracking keyboard animation is nil, means it is not + // created, bail out. + if ([flutterViewController keyboardAnimationView] == nil) { + return; + } + // If keyboardAnimationVSyncClient is nil, means the animation ends. + // And should bail out. + if (flutterViewController.get().keyboardAnimationVSyncClient == nil) { + return; + } - if ([flutterViewController keyboardAnimationView].superview == nil) { - // Ensure the keyboardAnimationView is in view hierarchy when animation running. - [flutterViewController.get().viewIfLoaded - addSubview:[flutterViewController keyboardAnimationView]]; - } + if ([flutterViewController keyboardAnimationView].superview == nil) { + // Ensure the keyboardAnimationView is in view hierarchy when animation running. + [flutterViewController.get().viewIfLoaded + addSubview:[flutterViewController keyboardAnimationView]]; + } - if ([flutterViewController keyboardSpringAnimation] == nil) { - if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - flutterViewController.get() - .keyboardAnimationView.layer.presentationLayer.frame.origin.y; - [flutterViewController updateViewportMetricsIfNeeded]; - } - } else { - fml::TimeDelta timeElapsed = - keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime; - flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = - [[flutterViewController keyboardSpringAnimation] - curveFunction:timeElapsed.ToSecondsF()]; - [flutterViewController updateViewportMetricsIfNeeded]; - } - }; + if ([flutterViewController keyboardSpringAnimation] == nil) { + if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) { + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + flutterViewController.get() + .keyboardAnimationView.layer.presentationLayer.frame.origin.y; + [flutterViewController updateViewportMetricsIfNeeded]; + } + } else { + fml::TimeDelta timeElapsed = + keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = + [[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()]; + [flutterViewController updateViewportMetricsIfNeeded]; + } + }; [self setupKeyboardAnimationVsyncClient:keyboardAnimationCallback]; VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient; @@ -1697,15 +1697,18 @@ - (void)setupKeyboardAnimationVsyncClient: @"_keyboardAnimationVSyncClient must be nil when setup"); // Make sure the new viewport metrics get sent after the begin frame event has processed. - auto uiCallback = [keyboardAnimationCallback, + auto uiCallback = [keyboardAnimationCallback = + (FlutterKeyboardAnimationCallback)[keyboardAnimationCallback copy], engine = _engine](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; [engine.get() platformTaskRunner]->PostTask( - [keyboardAnimationCallback, keyboardAnimationTargetTime] { - keyboardAnimationCallback(keyboardAnimationTargetTime); - }); + [keyboardAnimationCallback = + (FlutterKeyboardAnimationCallback)[keyboardAnimationCallback copy], + keyboardAnimationTargetTime] { keyboardAnimationCallback(keyboardAnimationTargetTime); }); + [keyboardAnimationCallback release]; }; + [keyboardAnimationCallback release]; _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine.get() uiTaskRunner] callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index 83e5ff1a6b358..5f9fa58418b1b 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -33,7 +33,7 @@ typedef NS_ENUM(NSInteger, FlutterKeyboardMode) { FlutterKeyboardModeFloating = 2, }; -typedef std::function FlutterKeyboardAnimationCallback; +typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); @interface FlutterViewController () From 5553b0a672c6c72a05bb8eccd6ae6e23657ff772 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Mon, 12 Jun 2023 09:16:01 +0800 Subject: [PATCH 17/23] copy callback --- .../ios/framework/Source/FlutterViewController.mm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index f6767db02fdaa..f576eed1672d9 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -75,6 +75,7 @@ @interface FlutterViewController () recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; [engine.get() platformTaskRunner]->PostTask( [keyboardAnimationCallback = (FlutterKeyboardAnimationCallback)[keyboardAnimationCallback copy], - keyboardAnimationTargetTime] { keyboardAnimationCallback(keyboardAnimationTargetTime); }); - [keyboardAnimationCallback release]; + keyboardAnimationTargetTime] { + keyboardAnimationCallback(keyboardAnimationTargetTime); + [keyboardAnimationCallback release]; + }); }; - [keyboardAnimationCallback release]; _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine.get() uiTaskRunner] callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; @@ -1719,6 +1722,8 @@ - (void)invalidateKeyboardAnimationVSyncClient { [_keyboardAnimationVSyncClient invalidate]; [_keyboardAnimationVSyncClient release]; _keyboardAnimationVSyncClient = nil; + [_keyboardAnimationCallback release]; + _keyboardAnimationCallback = nil; } - (void)removeKeyboardAnimationView { From 9dda593847b0ef66507ed7ae66974e621ce36686 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Mon, 12 Jun 2023 10:10:59 +0800 Subject: [PATCH 18/23] ++ --- .../darwin/ios/framework/Source/FlutterViewController.mm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index f576eed1672d9..aefdc7d997bb2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1611,7 +1611,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { } // If the view controller's view is not loaded, bail out. - if (flutterViewController.get().viewIfLoaded == nil) { + if (!flutterViewController.get().isViewLoaded) { return; } // If the view for tracking keyboard animation is nil, means it is not @@ -1627,8 +1627,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration { if ([flutterViewController keyboardAnimationView].superview == nil) { // Ensure the keyboardAnimationView is in view hierarchy when animation running. - [flutterViewController.get().viewIfLoaded - addSubview:[flutterViewController keyboardAnimationView]]; + [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; } if ([flutterViewController keyboardSpringAnimation] == nil) { From da2f85e6a351183ad16762e92c49f8785f3f5638 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Mon, 12 Jun 2023 14:48:22 +0800 Subject: [PATCH 19/23] ++ --- .../framework/Source/FlutterViewController.mm | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index aefdc7d997bb2..375999413e0e7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -69,13 +69,12 @@ @interface FlutterViewController () animationCallback( + [keyboardAnimationCallback copy]); + auto uiCallback = [animationCallback, engine = _engine](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; - [engine.get() platformTaskRunner]->PostTask( - [keyboardAnimationCallback = - (FlutterKeyboardAnimationCallback)[keyboardAnimationCallback copy], - keyboardAnimationTargetTime] { - keyboardAnimationCallback(keyboardAnimationTargetTime); - [keyboardAnimationCallback release]; - }); + [engine.get() platformTaskRunner]->PostTask([animationCallback, keyboardAnimationTargetTime] { + animationCallback.get()(keyboardAnimationTargetTime); + }); }; _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine.get() uiTaskRunner] callback:uiCallback]; @@ -1721,8 +1716,6 @@ - (void)invalidateKeyboardAnimationVSyncClient { [_keyboardAnimationVSyncClient invalidate]; [_keyboardAnimationVSyncClient release]; _keyboardAnimationVSyncClient = nil; - [_keyboardAnimationCallback release]; - _keyboardAnimationCallback = nil; } - (void)removeKeyboardAnimationView { From daf41e5f8f216d296035b8b1404b260da14db9ba Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Mon, 12 Jun 2023 16:23:54 +0800 Subject: [PATCH 20/23] unit test --- .../ios/framework/Source/FlutterViewControllerTest.mm | 6 +++--- .../ios/framework/Source/FlutterViewControllerTest_mrc.mm | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index afae0dea49027..73dd209704491 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -457,9 +457,9 @@ - (void)testKeyboardAnimationWillWaitUIThreadVsync { const int delayTime = 1; [engine uiTaskRunner]->PostTask([] { sleep(delayTime); }); XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"]; - CFTimeInterval fulfillTime; - FlutterKeyboardAnimationCallback callback = [&expectation, - &fulfillTime](fml::TimePoint targetTime) { + + __block CFTimeInterval fulfillTime; + FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) { fulfillTime = CACurrentMediaTime(); [expectation fulfill]; }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm index aa8ce406a50c8..c19486f0a9be1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm @@ -56,7 +56,8 @@ - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterV FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; - FlutterKeyboardAnimationCallback callback = [](fml::TimePoint targetTime) {}; + FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) { + }; [viewController setupKeyboardAnimationVsyncClient:callback]; XCTAssertNotNil(viewController.keyboardAnimationVSyncClient); CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink]; From 0e45ccbdf5930aeea1e45f58a274b79ed84c87e1 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 13 Jun 2023 08:35:56 +0800 Subject: [PATCH 21/23] ++ --- .../darwin/ios/framework/Source/FlutterViewController.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 375999413e0e7..bfc839385899a 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1706,8 +1706,11 @@ - (void)setupKeyboardAnimationVsyncClient: animationCallback.get()(keyboardAnimationTargetTime); }); }; + + flutter::Shell& shell = [_engine shell]; _keyboardAnimationVSyncClient = - [[VSyncClient alloc] initWithTaskRunner:[_engine.get() uiTaskRunner] callback:uiCallback]; + [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetUITaskRunner() + callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; [_keyboardAnimationVSyncClient await]; } From 62f60a752285828e1e02db706e7da73e9f7f4452 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 13 Jun 2023 08:59:10 +0800 Subject: [PATCH 22/23] ++ --- .../ios/framework/Source/FlutterViewController.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index bfc839385899a..cf74b23a334f1 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1695,19 +1695,21 @@ - (void)setupKeyboardAnimationVsyncClient: NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); + flutter::Shell& shell = [_engine shell]; + // Make sure the new viewport metrics get sent after the begin frame event has processed. fml::scoped_nsprotocol animationCallback( [keyboardAnimationCallback copy]); auto uiCallback = [animationCallback, - engine = _engine](std::unique_ptr recorder) { + &shell](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; - [engine.get() platformTaskRunner]->PostTask([animationCallback, keyboardAnimationTargetTime] { - animationCallback.get()(keyboardAnimationTargetTime); - }); + shell.GetTaskRunners().GetPlatformTaskRunner()->PostTask( + [animationCallback, keyboardAnimationTargetTime] { + animationCallback.get()(keyboardAnimationTargetTime); + }); }; - flutter::Shell& shell = [_engine shell]; _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetUITaskRunner() callback:uiCallback]; From 692a0ed0040aee442d5f281e152d45284d494fb7 Mon Sep 17 00:00:00 2001 From: luckysmg <2539699336@qq.com> Date: Tue, 13 Jun 2023 12:58:25 +0800 Subject: [PATCH 23/23] ++ --- .../framework/Source/FlutterViewController.mm | 16 ++++++---------- .../Source/FlutterViewControllerTest.mm | 14 ++++++-------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index cf74b23a334f1..c88366b669cc3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1695,24 +1695,20 @@ - (void)setupKeyboardAnimationVsyncClient: NSAssert(_keyboardAnimationVSyncClient == nil, @"_keyboardAnimationVSyncClient must be nil when setup"); - flutter::Shell& shell = [_engine shell]; - // Make sure the new viewport metrics get sent after the begin frame event has processed. fml::scoped_nsprotocol animationCallback( [keyboardAnimationCallback copy]); auto uiCallback = [animationCallback, - &shell](std::unique_ptr recorder) { + engine = _engine](std::unique_ptr recorder) { fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime(); fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval; - shell.GetTaskRunners().GetPlatformTaskRunner()->PostTask( - [animationCallback, keyboardAnimationTargetTime] { - animationCallback.get()(keyboardAnimationTargetTime); - }); + [engine platformTaskRunner]->PostTask([animationCallback, keyboardAnimationTargetTime] { + animationCallback.get()(keyboardAnimationTargetTime); + }); }; - _keyboardAnimationVSyncClient = - [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetUITaskRunner() - callback:uiCallback]; + _keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine uiTaskRunner] + callback:uiCallback]; _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; [_keyboardAnimationVSyncClient await]; } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 73dd209704491..e7b6880c56536 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -647,9 +647,9 @@ - (void)testCalculateKeyboardInset { } - (void)testHandleKeyboardNotification { - FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); - [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil]; // keyboard is empty @@ -670,11 +670,9 @@ - (void)testHandleKeyboardNotification { [self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame]; viewControllerMock.targetViewInsetBottom = 0; XCTestExpectation* expectation = [self expectationWithDescription:@"update viewport"]; - OCMStub([mockEngine updateViewportMetrics:flutter::ViewportMetrics()]) - .ignoringNonObjectArgs() - .andDo(^(NSInvocation* invocation) { - [expectation fulfill]; - }); + OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) { + [expectation fulfill]; + }); [viewControllerMock handleKeyboardNotification:notification]; XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * UIScreen.mainScreen.scale);