Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 3ade212

Browse files
author
Chris Yang
authored
Revert "[iOS][Keyboard] Wait vsync on UI thread and update viewport inset to avoid jitter." (#43422)
Reverts #42312 Original PR caused crash flutter/flutter#130028 Will reopen flutter/flutter#120555
1 parent c29be66 commit 3ade212

File tree

6 files changed

+67
-149
lines changed

6 files changed

+67
-149
lines changed

shell/platform/darwin/ios/framework/Source/FlutterEngine.mm

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,7 @@ - (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)p
333333
return _shell->GetTaskRunners().GetPlatformTaskRunner();
334334
}
335335

336-
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner {
337-
FML_DCHECK(_shell);
338-
return _shell->GetTaskRunners().GetUITaskRunner();
339-
}
340-
341-
- (fml::RefPtr<fml::TaskRunner>)rasterTaskRunner {
336+
- (fml::RefPtr<fml::TaskRunner>)RasterTaskRunner {
342337
FML_DCHECK(_shell);
343338
return _shell->GetTaskRunners().GetRasterTaskRunner();
344339
}

shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ extern NSString* const kFlutterEngineWillDealloc;
3939
- (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet;
4040

4141
- (fml::RefPtr<fml::TaskRunner>)platformTaskRunner;
42-
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
43-
- (fml::RefPtr<fml::TaskRunner>)rasterTaskRunner;
42+
- (fml::RefPtr<fml::TaskRunner>)RasterTaskRunner;
4443

4544
- (fml::WeakPtr<flutter::PlatformView>)platformView;
4645

shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

Lines changed: 42 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ @interface FlutterViewController () <FlutterBinaryMessenger, UIScrollViewDelegat
6969
/**
7070
* Keyboard animation properties
7171
*/
72-
@property(nonatomic, assign) CGFloat targetViewInsetBottom;
73-
@property(nonatomic, assign) CGFloat originalViewInsetBottom;
72+
@property(nonatomic, assign) double targetViewInsetBottom;
7473
@property(nonatomic, retain) VSyncClient* keyboardAnimationVSyncClient;
7574
@property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
7675
@property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
76+
@property(nonatomic, assign) CGFloat originalViewInsetBottom;
7777
@property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
7878

7979
/// VSyncClient for touch events delivery frame rate correction.
@@ -575,8 +575,8 @@ - (void)installFirstFrameCallback {
575575
// Start on the platform thread.
576576
weakPlatformView->SetNextFrameCallback([weakSelf = [self getWeakPtr],
577577
platformTaskRunner = [_engine.get() platformTaskRunner],
578-
rasterTaskRunner = [_engine.get() rasterTaskRunner]]() {
579-
FML_DCHECK(rasterTaskRunner->RunsTasksOnCurrentThread());
578+
RasterTaskRunner = [_engine.get() RasterTaskRunner]]() {
579+
FML_DCHECK(RasterTaskRunner->RunsTasksOnCurrentThread());
580580
// Get callback on raster thread and jump back to platform thread.
581581
platformTaskRunner->PostTask([weakSelf]() {
582582
if (weakSelf) {
@@ -1605,55 +1605,7 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
16051605

16061606
// Invalidate old vsync client if old animation is not completed.
16071607
[self invalidateKeyboardAnimationVSyncClient];
1608-
1609-
fml::WeakPtr<FlutterViewController> weakSelf = [self getWeakPtr];
1610-
FlutterKeyboardAnimationCallback keyboardAnimationCallback = ^(
1611-
fml::TimePoint keyboardAnimationTargetTime) {
1612-
if (!weakSelf) {
1613-
return;
1614-
}
1615-
fml::scoped_nsobject<FlutterViewController> flutterViewController(
1616-
[(FlutterViewController*)weakSelf.get() retain]);
1617-
if (!flutterViewController) {
1618-
return;
1619-
}
1620-
1621-
// If the view controller's view is not loaded, bail out.
1622-
if (!flutterViewController.get().isViewLoaded) {
1623-
return;
1624-
}
1625-
// If the view for tracking keyboard animation is nil, means it is not
1626-
// created, bail out.
1627-
if ([flutterViewController keyboardAnimationView] == nil) {
1628-
return;
1629-
}
1630-
// If keyboardAnimationVSyncClient is nil, means the animation ends.
1631-
// And should bail out.
1632-
if (flutterViewController.get().keyboardAnimationVSyncClient == nil) {
1633-
return;
1634-
}
1635-
1636-
if ([flutterViewController keyboardAnimationView].superview == nil) {
1637-
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
1638-
[flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
1639-
}
1640-
1641-
if ([flutterViewController keyboardSpringAnimation] == nil) {
1642-
if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
1643-
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1644-
flutterViewController.get()
1645-
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1646-
[flutterViewController updateViewportMetricsIfNeeded];
1647-
}
1648-
} else {
1649-
fml::TimeDelta timeElapsed =
1650-
keyboardAnimationTargetTime - flutterViewController.get().keyboardAnimationStartTime;
1651-
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1652-
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
1653-
[flutterViewController updateViewportMetricsIfNeeded];
1654-
}
1655-
};
1656-
[self setupKeyboardAnimationVsyncClient:keyboardAnimationCallback];
1608+
[self setupKeyboardAnimationVsyncClient];
16571609
VSyncClient* currentVsyncClient = _keyboardAnimationVSyncClient;
16581610

16591611
[UIView animateWithDuration:duration
@@ -1696,28 +1648,45 @@ - (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation {
16961648
toValue:self.targetViewInsetBottom]);
16971649
}
16981650

1699-
- (void)setupKeyboardAnimationVsyncClient:
1700-
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback {
1701-
if (!keyboardAnimationCallback) {
1702-
return;
1703-
}
1704-
NSAssert(_keyboardAnimationVSyncClient == nil,
1705-
@"_keyboardAnimationVSyncClient must be nil when setup");
1651+
- (void)setupKeyboardAnimationVsyncClient {
1652+
auto callback = [weakSelf =
1653+
[self getWeakPtr]](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1654+
if (!weakSelf) {
1655+
return;
1656+
}
1657+
fml::scoped_nsobject<FlutterViewController> flutterViewController(
1658+
[(FlutterViewController*)weakSelf.get() retain]);
1659+
if (!flutterViewController) {
1660+
return;
1661+
}
17061662

1707-
// Make sure the new viewport metrics get sent after the begin frame event has processed.
1708-
fml::scoped_nsprotocol<FlutterKeyboardAnimationCallback> animationCallback(
1709-
[keyboardAnimationCallback copy]);
1710-
auto uiCallback = [animationCallback,
1711-
engine = _engine](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
1712-
fml::TimeDelta frameInterval = recorder->GetVsyncTargetTime() - recorder->GetVsyncStartTime();
1713-
fml::TimePoint keyboardAnimationTargetTime = recorder->GetVsyncTargetTime() + frameInterval;
1714-
[engine platformTaskRunner]->PostTask([animationCallback, keyboardAnimationTargetTime] {
1715-
animationCallback.get()(keyboardAnimationTargetTime);
1716-
});
1717-
};
1663+
if ([flutterViewController keyboardAnimationView].superview == nil) {
1664+
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
1665+
[flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
1666+
}
17181667

1719-
_keyboardAnimationVSyncClient = [[VSyncClient alloc] initWithTaskRunner:[_engine uiTaskRunner]
1720-
callback:uiCallback];
1668+
if ([flutterViewController keyboardSpringAnimation] == nil) {
1669+
if (flutterViewController.get().keyboardAnimationView.layer.presentationLayer) {
1670+
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1671+
flutterViewController.get()
1672+
.keyboardAnimationView.layer.presentationLayer.frame.origin.y;
1673+
[flutterViewController updateViewportMetricsIfNeeded];
1674+
}
1675+
} else {
1676+
fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() -
1677+
flutterViewController.get().keyboardAnimationStartTime;
1678+
1679+
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
1680+
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
1681+
[flutterViewController updateViewportMetricsIfNeeded];
1682+
}
1683+
};
1684+
flutter::Shell& shell = [_engine.get() shell];
1685+
NSAssert(_keyboardAnimationVSyncClient == nil,
1686+
@"_keyboardAnimationVSyncClient must be nil when setup");
1687+
_keyboardAnimationVSyncClient =
1688+
[[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner()
1689+
callback:callback];
17211690
_keyboardAnimationVSyncClient.allowPauseAfterVsync = NO;
17221691
[_keyboardAnimationVSyncClient await];
17231692
}

shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ - (FlutterTextInputPlugin*)textInputPlugin;
2626
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
2727
callback:(nullable FlutterKeyEventCallback)callback
2828
userData:(nullable void*)userData;
29-
- (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
3029
@end
3130

3231
/// Sometimes we have to use a custom mock to avoid retain cycles in OCMock.
@@ -136,11 +135,10 @@ - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
136135
- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
137136
- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
138137
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
138+
- (void)setupKeyboardAnimationVsyncClient;
139139
- (UIView*)keyboardAnimationView;
140140
- (SpringAnimation*)keyboardSpringAnimation;
141141
- (void)setupKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
142-
- (void)setupKeyboardAnimationVsyncClient:
143-
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
144142
- (void)ensureViewportMetricsIsCorrect;
145143
- (void)invalidateKeyboardAnimationVSyncClient;
146144
- (void)addInternalPlugins;
@@ -199,6 +197,18 @@ - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
199197
OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
200198
}
201199

200+
- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient {
201+
FlutterEngine* engine = [[FlutterEngine alloc] init];
202+
[engine runWithEntrypoint:nil];
203+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
204+
nibName:nil
205+
bundle:nil];
206+
FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
207+
viewControllerMock.targetViewInsetBottom = 100;
208+
[viewControllerMock startKeyBoardAnimation:0.25];
209+
OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]);
210+
}
211+
202212
- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
203213
FlutterEngine* engine = [[FlutterEngine alloc] init];
204214
[engine runWithEntrypoint:nil];
@@ -441,34 +451,6 @@ - (void)testShouldIgnoreKeyboardNotification {
441451
}
442452
}
443453

444-
- (void)testKeyboardAnimationWillWaitUIThreadVsync {
445-
// We need to make sure the new viewport metrics get sent after the
446-
// begin frame event has processed. And this test is to expect that the callback
447-
// will sync with UI thread. So just simulate a lot of works on UI thread and
448-
// test the keyboard animation callback will execute until UI task completed.
449-
// Related issue: https://github.com/flutter/flutter/issues/120555.
450-
451-
FlutterEngine* engine = [[FlutterEngine alloc] init];
452-
[engine runWithEntrypoint:nil];
453-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
454-
nibName:nil
455-
bundle:nil];
456-
// Post a task to UI thread to block the thread.
457-
const int delayTime = 1;
458-
[engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
459-
XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"];
460-
461-
__block CFTimeInterval fulfillTime;
462-
FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) {
463-
fulfillTime = CACurrentMediaTime();
464-
[expectation fulfill];
465-
};
466-
CFTimeInterval startTime = CACurrentMediaTime();
467-
[viewController setupKeyboardAnimationVsyncClient:callback];
468-
[self waitForExpectationsWithTimeout:5.0 handler:nil];
469-
XCTAssertTrue(fulfillTime - startTime > delayTime);
470-
}
471-
472454
- (void)testCalculateKeyboardAttachMode {
473455
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
474456
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
@@ -647,9 +629,9 @@ - (void)testCalculateKeyboardInset {
647629
}
648630

649631
- (void)testHandleKeyboardNotification {
650-
FlutterEngine* engine = [[FlutterEngine alloc] init];
651-
[engine runWithEntrypoint:nil];
652-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
632+
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
633+
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
634+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
653635
nibName:nil
654636
bundle:nil];
655637
// keyboard is empty
@@ -670,9 +652,11 @@ - (void)testHandleKeyboardNotification {
670652
[self setupMockMainScreenAndView:viewControllerMock viewFrame:viewFrame convertedFrame:viewFrame];
671653
viewControllerMock.targetViewInsetBottom = 0;
672654
XCTestExpectation* expectation = [self expectationWithDescription:@"update viewport"];
673-
OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
674-
[expectation fulfill];
675-
});
655+
OCMStub([mockEngine updateViewportMetrics:flutter::ViewportMetrics()])
656+
.ignoringNonObjectArgs()
657+
.andDo(^(NSInvocation* invocation) {
658+
[expectation fulfill];
659+
});
676660

677661
[viewControllerMock handleKeyboardNotification:notification];
678662
XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * UIScreen.mainScreen.scale);

shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
99
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
10-
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
1110
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
1211

1312
FLUTTER_ASSERT_NOT_ARC
@@ -32,9 +31,7 @@ @interface FlutterViewController (Testing)
3231
@property(nonatomic, retain) VSyncClient* touchRateCorrectionVSyncClient;
3332

3433
- (void)createTouchRateCorrectionVSyncClientIfNeeded;
35-
- (void)setupKeyboardAnimationVsyncClient:
36-
(FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
37-
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
34+
- (void)setupKeyboardAnimationVsyncClient;
3835
- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
3936

4037
@end
@@ -56,9 +53,7 @@ - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterV
5653
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
5754
nibName:nil
5855
bundle:nil];
59-
FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) {
60-
};
61-
[viewController setupKeyboardAnimationVsyncClient:callback];
56+
[viewController setupKeyboardAnimationVsyncClient];
6257
XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
6358
CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
6459
XCTAssertNotNil(link);
@@ -178,26 +173,4 @@ - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
178173
XCTAssertFalse(link.isPaused);
179174
}
180175

181-
- (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
182-
FlutterEngine* engine = [[FlutterEngine alloc] init];
183-
[engine runWithEntrypoint:nil];
184-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
185-
nibName:nil
186-
bundle:nil];
187-
viewController.targetViewInsetBottom = 100;
188-
[viewController startKeyBoardAnimation:0.25];
189-
XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
190-
}
191-
192-
- (void)
193-
testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
194-
FlutterEngine* engine = [[FlutterEngine alloc] init];
195-
[engine runWithEntrypoint:nil];
196-
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
197-
nibName:nil
198-
bundle:nil];
199-
[viewController setupKeyboardAnimationVsyncClient:nil];
200-
XCTAssertNil(viewController.keyboardAnimationVSyncClient);
201-
}
202-
203176
@end

shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ typedef NS_ENUM(NSInteger, FlutterKeyboardMode) {
3333
FlutterKeyboardModeFloating = 2,
3434
};
3535

36-
typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint);
37-
3836
@interface FlutterViewController () <FlutterViewResponder>
3937

4038
@property(class, nonatomic, readonly) BOOL accessibilityIsOnOffSwitchLabelsEnabled;

0 commit comments

Comments
 (0)