-
Notifications
You must be signed in to change notification settings - Fork 6k
Fix issues related to keyboard inset #37719
Changes from 6 commits
081a812
8501ff2
0c0cc73
078308c
6f16d3d
79705b4
6270428
084b228
cbece73
6c4f65e
39313a7
602c703
ea788a1
179f510
5aa9221
010e86b
aa6276e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -315,6 +315,11 @@ - (void)setupNotificationCenterObservers { | |
| name:UIKeyboardWillChangeFrameNotification | ||
| object:nil]; | ||
|
|
||
| [center addObserver:self | ||
| selector:@selector(keyboardWillShowNotification:) | ||
| name:UIKeyboardWillShowNotification | ||
| object:nil]; | ||
|
|
||
| [center addObserver:self | ||
| selector:@selector(keyboardWillBeHidden:) | ||
| name:UIKeyboardWillHideNotification | ||
|
|
@@ -1272,43 +1277,67 @@ - (void)updateViewportPadding { | |
|
|
||
| #pragma mark - Keyboard events | ||
|
|
||
| - (void)keyboardWillChangeFrame:(NSNotification*)notification { | ||
| - (void)keyboardWillShowNotification:(NSNotification*)notification { | ||
| // Immediately prior to a docked keyboard being shown or when a keyboard goes from | ||
| // undocked/floating to docked, this notification is triggered | ||
|
|
||
| NSDictionary* info = [notification userInfo]; | ||
| CGRect keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; | ||
| bool isEmpty = CGRectIsEmpty(keyboardFrame); | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
|
|
||
| // Ignore keyboard notifications related to other apps. | ||
| id isLocal = info[UIKeyboardIsLocalUserInfoKey]; | ||
| if (isLocal && ![isLocal boolValue]) { | ||
| return; | ||
| } | ||
| // If keyboard is empty, bypass check if it's from another app | ||
| if (isEmpty == false) { | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| // Ignore keyboard notifications related to other apps. | ||
| id isLocal = info[UIKeyboardIsLocalUserInfoKey]; | ||
| if (isLocal && ![isLocal boolValue]) { | ||
| return; | ||
| } | ||
|
|
||
| // Ignore keyboard notifications if engine’s viewController is not current viewController. | ||
| if ([_engine.get() viewController] != self) { | ||
| return; | ||
| // Ignore keyboard notifications if engine’s viewController is not current viewController. | ||
| if ([_engine.get() viewController] != self) { | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| CGRect keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; | ||
| CGRect screenRect = [[UIScreen mainScreen] bounds]; | ||
|
|
||
| // Get the animation duration | ||
| // In Slide Over view, the keyboard's dimensions/position does not include the space | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| // below the app, even though the keyboard may be at the bottom of the screen. | ||
| // To handle, shift the Y origin by the amount of space below the app. | ||
| CGFloat screenHeight = CGRectGetHeight(screenRect); | ||
| CGFloat screenWidth = CGRectGetWidth(screenRect); | ||
| CGFloat appHeight = CGRectGetHeight(self.view.window.frame); | ||
| CGFloat appWidth = CGRectGetWidth(self.view.window.frame); | ||
| if (self.view.safeAreaInsets.bottom > 0 && appWidth < screenWidth) { | ||
| // In Slide Over view, the app is vertically centered with space above and below, | ||
| // which is why we divide by 2 to get the space below. | ||
| keyboardFrame.origin.y += (screenHeight - appHeight) / 2; | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // If keyboard is within the screen and it's not empty, calculate and set the inset. | ||
| // If keyboard is not within the screen (it's usually below), set inset to 0. | ||
| // If keyboard frame is empty (usually because it was dragged and dropped), set inset to 0. | ||
| CGFloat calculatedInset = 0; | ||
| if (CGRectIntersectsRect(keyboardFrame, screenRect) && !isEmpty) { | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| calculatedInset = [self calculateKeyboardInset:screenRect keyboardFrame:keyboardFrame]; | ||
| } | ||
|
|
||
| // avoid double triggering startKeyBoardAnimation | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| if (self.targetViewInsetBottom == calculatedInset) { | ||
| return; | ||
| } | ||
|
|
||
| self.targetViewInsetBottom = calculatedInset; | ||
| NSTimeInterval duration = | ||
| [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; | ||
|
|
||
| // Considering the iPad's split keyboard, Flutter needs to check if the keyboard frame is present | ||
| // in the screen to see if the keyboard is visible. | ||
| if (CGRectIntersectsRect(keyboardFrame, screenRect)) { | ||
| CGFloat bottom = CGRectGetHeight(keyboardFrame); | ||
| CGFloat scale = [UIScreen mainScreen].scale; | ||
| // The keyboard is treated as an inset since we want to effectively reduce the window size by | ||
| // the keyboard height. The Dart side will compute a value accounting for the keyboard-consuming | ||
| // bottom padding. | ||
| self.targetViewInsetBottom = bottom * scale; | ||
| } else { | ||
| self.targetViewInsetBottom = 0; | ||
| } | ||
| [self startKeyBoardAnimation:duration]; | ||
| } | ||
|
|
||
| - (void)keyboardWillBeHidden:(NSNotification*)notification { | ||
| - (void)keyboardWillChangeFrame:(NSNotification*)notification { | ||
| // Immediately prior to a change in keyboard frame, this notification is triggered. | ||
| // There are some cases where UIKeyboardWillShowNotification & UIKeyboardWillHideNotification | ||
| // do not act as expected and this is used to catch those cases. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the change frame notification work in all cases? Maybe we should eliminate Could be a nice refactor in a follow up PR if we can use frame change notification for all cases.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So none of them are very reliable which is why I used all three of them. Here's some of the use cases for example
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, gotcha. I think logic this complex deserves a design documentation :) And we should have a comment in the code to link to the design doc. (The same way PlatformView should have and never did :p ) I think for this PR, it would be nice to explain "some cases" a little more in the comments. Or add a link in the code to your comment here: #37719 (comment) |
||
|
|
||
| NSDictionary* info = [notification userInfo]; | ||
|
|
||
| // Ignore keyboard notifications related to other apps. | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
|
|
@@ -1322,10 +1351,85 @@ - (void)keyboardWillBeHidden:(NSNotification*)notification { | |
| return; | ||
| } | ||
|
|
||
| CGRect keyboardFrame = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; | ||
|
|
||
| // Ignore notification when keyboard has zero width/height | ||
| // This happens when keyboard is dragged. | ||
| if (CGRectIsEmpty(keyboardFrame)) { | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| return; | ||
| } | ||
|
|
||
| CGRect screenRect = [[UIScreen mainScreen] bounds]; | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| CGFloat screenHeight = CGRectGetHeight(screenRect); | ||
| CGRect keyboardBeginFrame = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]; | ||
| CGFloat keyboardBeginWidth = CGRectGetWidth(keyboardBeginFrame); | ||
|
|
||
| // Ignore notification when keyboard is in process of being rotated. | ||
| // When the keyboard's width at the beginning of the animation equals the screen's | ||
| // current height, we can assume the keyboard was rotated. | ||
| if (screenHeight == keyboardBeginWidth) { | ||
| return; | ||
| } | ||
|
|
||
| // In Slide Over view, the keyboard's dimensions/position does not include the space | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
| // below the app, even though the keyboard may be at the bottom of the screen. | ||
| // To handle, shift the Y origin by the amount of space below the app. | ||
| CGFloat screenWidth = CGRectGetWidth(screenRect); | ||
| CGFloat appHeight = CGRectGetHeight(self.view.window.frame); | ||
| CGFloat appWidth = CGRectGetWidth(self.view.window.frame); | ||
| if (self.view.safeAreaInsets.bottom > 0 && appWidth < screenWidth) { | ||
| // In Slide Over view, the app is vertically centered with space above and below, | ||
| // which is why we divide by 2 to get the space below. | ||
| keyboardFrame.origin.y += (screenHeight - appHeight) / 2; | ||
| } | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
|
|
||
| // If the keyboard is partially or fully showing at the bottom of the screen, | ||
| // calculate and set the inset. | ||
| // When the keyboard goes from docked to the floating small keyboard, it sometimes | ||
| // does not send a UIKeyboardWillHideNotification notification as expected. | ||
| // To handle, if the keyboard is above the bottom of the screen, set the inset to 0. | ||
| // If keyboard is not within the screen (it's usually below), set inset to 0. | ||
| CGFloat calculatedInset = 0; | ||
| CGFloat keyboardBottom = CGRectGetMaxY(keyboardFrame); | ||
| if (keyboardBottom >= screenHeight && CGRectIntersectsRect(keyboardFrame, screenRect)) { | ||
| calculatedInset = [self calculateKeyboardInset:screenRect keyboardFrame:keyboardFrame]; | ||
| } | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
|
|
||
| // avoid double triggering startKeyBoardAnimation | ||
| if (self.targetViewInsetBottom == calculatedInset) { | ||
| return; | ||
| } | ||
|
|
||
| self.targetViewInsetBottom = calculatedInset; | ||
| NSTimeInterval duration = | ||
| [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; | ||
| [self startKeyBoardAnimation:duration]; | ||
| } | ||
|
|
||
| - (CGFloat)calculateKeyboardInset:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame { | ||
| // Sometimes when rotating orientation, the keyboard height will be higher than it really is. | ||
| // So calculate how much of the keyboard is showing using position. | ||
| CGFloat screenHeight = CGRectGetHeight(screenRect); | ||
| CGFloat keyboardTop = CGRectGetMinY(keyboardFrame); | ||
| CGFloat portionOfKeyboardShowing = screenHeight - keyboardTop; | ||
|
vashworth marked this conversation as resolved.
Outdated
|
||
|
|
||
| // The keyboard is treated as an inset since we want to effectively reduce the window size by | ||
| // the keyboard height. The Dart side will compute a value accounting for the keyboard-consuming | ||
| // bottom padding. | ||
| CGFloat scale = [UIScreen mainScreen].scale; | ||
| CGFloat calculatedInset = portionOfKeyboardShowing * scale; | ||
|
|
||
| return calculatedInset; | ||
| } | ||
|
|
||
| - (void)keyboardWillBeHidden:(NSNotification*)notification { | ||
| // When keyboard is hidden or undocked, this notification will be triggered | ||
| if (self.targetViewInsetBottom != 0) { | ||
| // Ensure the keyboard will be dismissed. Just like the keyboardWillChangeFrame, | ||
| // keyboardWillBeHidden is also in an animation block in iOS sdk, so we don't need to set the | ||
| // animation curve. Related issue: https://github.com/flutter/flutter/issues/99951 | ||
| // Ensure the keyboard will be dismissed. Just like keyboardWillShowNotification | ||
| // and keyboardWillChangeFrame, keyboardWillBeHidden is also in an animation | ||
| // block in iOS sdk, so we don't need to set the animation curve. | ||
| // Related issue: https://github.com/flutter/flutter/issues/99951 | ||
| NSDictionary* info = [notification userInfo]; | ||
| self.targetViewInsetBottom = 0; | ||
| NSTimeInterval duration = | ||
| [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.