-
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
Show resolved
Hide resolved
|
||
|
|
||
| // 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
Show resolved
Hide resolved
|
||
| // 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
Show resolved
Hide resolved
|
||
| // 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
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // 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
Show resolved
Hide resolved
|
||
| calculatedInset = [self calculateKeyboardInset:screenRect keyboardFrame:keyboardFrame]; | ||
| } | ||
|
|
||
| // avoid double triggering startKeyBoardAnimation | ||
vashworth marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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. | ||
|
||
|
|
||
| NSDictionary* info = [notification userInfo]; | ||
|
|
||
| // Ignore keyboard notifications related to other apps. | ||
vashworth marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
@@ -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
Show resolved
Hide resolved
|
||
| return; | ||
| } | ||
|
|
||
| CGRect screenRect = [[UIScreen mainScreen] bounds]; | ||
vashworth marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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
Show resolved
Hide resolved
|
||
| // 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
Show resolved
Hide resolved
|
||
|
|
||
| // 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
Show resolved
Hide resolved
|
||
|
|
||
| // 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
Show resolved
Hide resolved
|
||
|
|
||
| // 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.