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

iOS keyboard animation synchronization #37604

Merged
merged 71 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
8472d6d
first keyboard commit
CoolDude53 Nov 14, 2022
c6dab59
first keyboard commit
CoolDude53 Nov 14, 2022
11bab51
Merge branch 'main' of github.com:CoolDude53/engine into ios-keyboard…
CoolDude53 Nov 14, 2022
6e64b0d
cleaned up from initial testing
CoolDude53 Nov 14, 2022
142a133
added keyboard animation stop calculation
CoolDude53 Nov 24, 2022
96c4c5d
added keyboard animation stop calculation
CoolDude53 Nov 24, 2022
eff2b16
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Nov 24, 2022
cc31006
lucky updates :)
CoolDude53 Nov 24, 2022
b1f484e
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Nov 24, 2022
ad15be4
type change
CoolDude53 Nov 24, 2022
3e1a279
Merge branch 'ios-keyboard-animation' of github.com:CoolDude53/engine…
CoolDude53 Nov 24, 2022
a5027d3
final touches
CoolDude53 Nov 27, 2022
84d7b8e
final touches
CoolDude53 Nov 27, 2022
1e208d1
final touches
CoolDude53 Nov 27, 2022
756ae31
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Nov 27, 2022
2b3743e
removed KeyboardAnimationView class
CoolDude53 Dec 1, 2022
b38cad0
removed KeyboardAnimationView class
CoolDude53 Dec 1, 2022
489bd1d
Merge branch 'ios-keyboard-animation' of github.com:CoolDude53/engine…
CoolDude53 Dec 1, 2022
0aeccc9
dynamic keyboard spring curve implementation
CoolDude53 Dec 2, 2022
1cd784a
broke out spring curve to own objc files
CoolDude53 Dec 2, 2022
ef8481c
broke out spring curve to own objc files
CoolDude53 Dec 2, 2022
582f2e0
simplified and added test
CoolDude53 Dec 8, 2022
3179417
modified spring formula to use damping
CoolDude53 Dec 8, 2022
467e1ce
added logic for compounding simultaneous animation calls
CoolDude53 Dec 8, 2022
98ee432
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Dec 8, 2022
7436731
remove unnecessary code
CoolDude53 Dec 8, 2022
ace0211
cleanup
CoolDude53 Dec 9, 2022
c285ad9
cleanup
CoolDude53 Dec 9, 2022
49abfc3
update springCurveIos file
CoolDude53 Dec 9, 2022
0fb7a03
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Dec 12, 2022
3cc8638
cleaning up
CoolDude53 Dec 13, 2022
b6b8747
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Dec 13, 2022
20ffdc9
simple optimizations
CoolDude53 Dec 13, 2022
1abe18d
comment update
CoolDude53 Dec 13, 2022
3e5ef3f
bool update
CoolDude53 Dec 13, 2022
7c335d5
Merge remote-tracking branch 'upstream/main' into ios-keyboard-animation
CoolDude53 Dec 16, 2022
d764770
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Dec 16, 2022
328b166
credit spring calculation project
CoolDude53 Dec 16, 2022
99f5d15
improved setupKeyboardAnimationCureveIfNeeded tests
CoolDude53 Dec 21, 2022
03c1f75
Optimized compounding animation checks and added new tests
CoolDude53 Dec 21, 2022
c143f19
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Dec 21, 2022
c03d642
changed viewport update logic
CoolDude53 Dec 22, 2022
fd2eaee
Update FlutterViewControllerTest.mm
CoolDude53 Jan 2, 2023
33ca106
Updated license
CoolDude53 Jan 2, 2023
934bcf8
Updated tests
CoolDude53 Jan 3, 2023
5f7d563
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Jan 3, 2023
76245e7
Cleanup
CoolDude53 Jan 4, 2023
d5a506f
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Jan 4, 2023
e70e1bc
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Jan 4, 2023
73062e7
Migrated spring class based on React
CoolDude53 Jan 4, 2023
b2a92dd
Updated syntax
CoolDude53 Jan 4, 2023
61b70f6
Update shell/platform/darwin/ios/framework/Source/spring_curve_ios.mm
CoolDude53 Jan 4, 2023
059bb70
Update shell/platform/darwin/ios/framework/Source/spring_curve_ios.h
CoolDude53 Jan 4, 2023
9c78679
Updated naming
CoolDude53 Jan 4, 2023
ebcb722
Updated naming
CoolDude53 Jan 4, 2023
3728335
Optimized spring model calculations
CoolDude53 Jan 4, 2023
6d02512
Improved interpolation accuracy
CoolDude53 Jan 5, 2023
3530db3
Revert "Improved interpolation accuracy"
CoolDude53 Jan 5, 2023
fb497a4
Updated comment
CoolDude53 Jan 5, 2023
3dcb341
Updated spring curve comments
CoolDude53 Jan 5, 2023
bc22eef
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Feb 2, 2023
b797c91
updated to work with third_party spring animation
CoolDude53 Feb 2, 2023
f4556a8
updated to use SpringAnimation to/from values
CoolDude53 Feb 2, 2023
b9d0d78
updated license (removed old files)
CoolDude53 Feb 3, 2023
f35a1e8
updates
CoolDude53 Feb 3, 2023
4ed2380
allow updating springanimation position values
CoolDude53 Feb 3, 2023
29b8ace
updated test
CoolDude53 Feb 3, 2023
13faeec
update
CoolDude53 Feb 3, 2023
4682d80
fixes
CoolDude53 Feb 3, 2023
bc298b3
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Feb 6, 2023
2fe2cf6
Merge branch 'flutter:main' into ios-keyboard-animation
CoolDude53 Feb 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions shell/platform/darwin/ios/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ source_set("flutter_framework_source") {
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/shell/profiling:profiling",
"//flutter/third_party/spring_animation",
"//third_party/skia",
]

Expand Down Expand Up @@ -301,6 +302,7 @@ shared_library("ios_test_flutter") {
"//flutter/shell/platform/darwin/common:framework_shared",
"//flutter/shell/platform/embedder:embedder_as_internal_library",
"//flutter/shell/platform/embedder:embedder_test_utils",
"//flutter/third_party/spring_animation",
"//flutter/third_party/tonic",
"//flutter/third_party/txt",
"//third_party/ocmock:ocmock_shared",
Expand Down
84 changes: 73 additions & 11 deletions shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
#import "flutter/shell/platform/embedder/embedder.h"
#import "flutter/third_party/spring_animation/spring_animation.h"

static constexpr int kMicrosecondsPerSecond = 1000 * 1000;
static constexpr CGFloat kScrollViewContentSize = 2.0;
Expand Down Expand Up @@ -65,6 +66,9 @@ @interface FlutterViewController () <FlutterBinaryMessenger, UIScrollViewDelegat
*/
@property(nonatomic, assign) double targetViewInsetBottom;
@property(nonatomic, retain) VSyncClient* keyboardAnimationVSyncClient;
@property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
@property(nonatomic, assign) fml::TimePoint keyboardAnimationStartTime;
@property(nonatomic, assign) CGFloat originalViewInsetBottom;
@property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;

/// VSyncClient for touch events delivery frame rate correction.
Expand Down Expand Up @@ -123,6 +127,7 @@ @implementation FlutterViewController {
// https://github.com/flutter/flutter/issues/35050
fml::scoped_nsobject<UIScrollView> _scrollView;
fml::scoped_nsobject<UIView> _keyboardAnimationView;
fml::scoped_nsobject<SpringAnimation> _keyboardSpringAnimation;
MouseState _mouseState;
// Timestamp after which a scroll inertia cancel event should be inferred.
NSTimeInterval _scrollInertiaEventStartline;
Expand Down Expand Up @@ -594,6 +599,10 @@ - (UIView*)keyboardAnimationView {
return _keyboardAnimationView.get();
}

- (SpringAnimation*)keyboardSpringAnimation {
return _keyboardSpringAnimation.get();
}

- (UIScreen*)mainScreenIfViewLoaded {
if (@available(iOS 13.0, *)) {
if (self.viewIfLoaded == nil) {
Expand Down Expand Up @@ -1314,13 +1323,14 @@ - (void)keyboardWillBeHidden:(NSNotification*)notification {
}

- (void)handleKeyboardNotification:(NSNotification*)notification {
// See https:://flutter.dev/go/ios-keyboard-calculating-inset for more details
// See https://flutter.dev/go/ios-keyboard-calculating-inset for more details
// on why notifications are used and how things are calculated.
if ([self shouldIgnoreKeyboardNotification:notification]) {
return;
}

NSDictionary* info = notification.userInfo;
CGRect beginKeyboardFrame = [info[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect keyboardFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
FlutterKeyboardMode keyboardMode = [self calculateKeyboardAttachMode:notification];
CGFloat calculatedInset = [self calculateKeyboardInset:keyboardFrame keyboardMode:keyboardMode];
Expand All @@ -1332,7 +1342,24 @@ - (void)handleKeyboardNotification:(NSNotification*)notification {

self.targetViewInsetBottom = calculatedInset;
NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[self startKeyBoardAnimation:duration];

// Flag for simultaneous compounding animation calls.
// This captures animation calls made while the keyboard animation is currently animating. If the
// new animation is in the same direction as the current animation, this flag lets the current
// animation continue with an updated targetViewInsetBottom instead of starting a new keyboard
// animation. This allows for smoother keyboard animation interpolation.
BOOL keyboardWillShow = beginKeyboardFrame.origin.y > keyboardFrame.origin.y;
BOOL keyboardAnimationIsCompounding =
self.keyboardAnimationIsShowing == keyboardWillShow && _keyboardAnimationVSyncClient != nil;

// Mark keyboard as showing or hiding.
self.keyboardAnimationIsShowing = keyboardWillShow;

if (!keyboardAnimationIsCompounding) {
[self startKeyBoardAnimation:duration];
} else if ([self keyboardSpringAnimation]) {
[self keyboardSpringAnimation].toValue = self.targetViewInsetBottom;
}
}

- (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification {
Expand Down Expand Up @@ -1494,12 +1521,12 @@ - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(NSInteger)
}

- (void)startKeyBoardAnimation:(NSTimeInterval)duration {
// If current physical_view_inset_bottom == targetViewInsetBottom,do nothing.
// If current physical_view_inset_bottom == targetViewInsetBottom, do nothing.
if (_viewportMetrics.physical_view_inset_bottom == self.targetViewInsetBottom) {
return;
}

// When call this method first time,
// When this method is called for the first time,
// initialize the keyboardAnimationView to get animation interpolation during animation.
if ([self keyboardAnimationView] == nil) {
UIView* keyboardAnimationView = [[UIView alloc] init];
Expand All @@ -1514,9 +1541,11 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
// Remove running animation when start another animation.
[[self keyboardAnimationView].layer removeAllAnimations];

// Set animation begin value.
// Set animation begin value and DisplayLink tracking values.
[self keyboardAnimationView].frame =
CGRectMake(0, _viewportMetrics.physical_view_inset_bottom, 0, 0);
self.keyboardAnimationStartTime = fml::TimePoint().Now();
self.originalViewInsetBottom = _viewportMetrics.physical_view_inset_bottom;

// Invalidate old vsync client if old animation is not completed.
[self invalidateKeyboardAnimationVSyncClient];
Expand All @@ -1527,6 +1556,11 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
animations:^{
// Set end value.
[self keyboardAnimationView].frame = CGRectMake(0, self.targetViewInsetBottom, 0, 0);

// Setup keyboard animation interpolation.
CAAnimation* keyboardAnimation =
[[self keyboardAnimationView].layer animationForKey:@"position"];
[self setupKeyboardSpringAnimationIfNeeded:keyboardAnimation];
}
completion:^(BOOL finished) {
if (_keyboardAnimationVSyncClient == currentVsyncClient) {
Expand All @@ -1540,6 +1574,24 @@ - (void)startKeyBoardAnimation:(NSTimeInterval)duration {
}];
}

- (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]]) {
_keyboardSpringAnimation.reset();
return;
}

// Setup keyboard spring animation details for spring curve animation calculation.
CASpringAnimation* keyboardCASpringAnimation = (CASpringAnimation*)keyboardAnimation;
_keyboardSpringAnimation.reset([[SpringAnimation alloc]
initWithStiffness:keyboardCASpringAnimation.stiffness
damping:keyboardCASpringAnimation.damping
mass:keyboardCASpringAnimation.mass
initialVelocity:keyboardCASpringAnimation.initialVelocity
fromValue:self.originalViewInsetBottom
toValue:self.targetViewInsetBottom]);
}

- (void)setupKeyboardAnimationVsyncClient {
auto callback = [weakSelf =
[self getWeakPtr]](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
Expand All @@ -1556,10 +1608,20 @@ - (void)setupKeyboardAnimationVsyncClient {
// Ensure the keyboardAnimationView is in view hierarchy when animation running.
[flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]];
}
if ([flutterViewController keyboardAnimationView].layer.presentationLayer) {
CGFloat value =
[flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y;
flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = value;

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 updateViewportMetrics];
}
} else {
fml::TimeDelta timeElapsed = recorder.get()->GetVsyncTargetTime() -
flutterViewController.get().keyboardAnimationStartTime;

flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom =
[[flutterViewController keyboardSpringAnimation] curveFunction:timeElapsed.ToSecondsF()];
[flutterViewController updateViewportMetrics];
}
};
Expand Down Expand Up @@ -1913,8 +1975,8 @@ - (BOOL)isAlwaysUse24HourFormat {
}

// The brightness mode of the platform, e.g., light or dark, expressed as a string that
// is understood by the Flutter framework. See the settings system channel for more
// information.
// is understood by the Flutter framework. See the settings
// system channel for more information.
- (NSString*)brightnessMode {
if (@available(iOS 13, *)) {
UIUserInterfaceStyle style = self.traitCollection.userInterfaceStyle;
Expand Down
Loading