Skip to content

Commit 7ebc773

Browse files
authored
[iOS] Ensure the keyboard bottom inset is zero when keyboard dismiss. (flutter#31982)
1 parent 54b098f commit 7ebc773

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,8 +1180,30 @@ - (void)keyboardWillChangeFrame:(NSNotification*)notification {
11801180
}
11811181

11821182
- (void)keyboardWillBeHidden:(NSNotification*)notification {
1183-
// When keyboard hide, the keyboardWillChangeFrame function will be called to update viewport
1184-
// metrics. So do not call [self updateViewportMetrics] here again.
1183+
NSDictionary* info = [notification userInfo];
1184+
1185+
if (@available(iOS 9, *)) {
1186+
// Ignore keyboard notifications related to other apps.
1187+
id isLocal = info[UIKeyboardIsLocalUserInfoKey];
1188+
if (isLocal && ![isLocal boolValue]) {
1189+
return;
1190+
}
1191+
}
1192+
1193+
// Ignore keyboard notifications if engine’s viewController is not current viewController.
1194+
if ([_engine.get() viewController] != self) {
1195+
return;
1196+
}
1197+
1198+
if (self.targetViewInsetBottom != 0) {
1199+
// Ensure the keyboard will be dismissed. Just like the keyboardWillChangeFrame,
1200+
// keyboardWillBeHidden is also in an animation block in iOS sdk, so we don't need to set the
1201+
// animation curve. Related issue: https://github.com/flutter/flutter/issues/99951
1202+
self.targetViewInsetBottom = 0;
1203+
NSTimeInterval duration =
1204+
[[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
1205+
[self startKeyBoardAnimation:duration];
1206+
}
11851207
}
11861208

11871209
- (void)startKeyBoardAnimation:(NSTimeInterval)duration {

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ @interface FlutterEmbedderKeyResponder (Tests)
125125
@end
126126

127127
@interface FlutterViewController (Tests)
128+
129+
@property(nonatomic, assign) double targetViewInsetBottom;
130+
128131
- (void)surfaceUpdated:(BOOL)appeared;
129132
- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
130133
- (void)handlePressEvent:(FlutterUIPressProxy*)press
@@ -135,6 +138,7 @@ - (void)onUserSettingsChanged:(NSNotification*)notification;
135138
- (void)applicationWillTerminate:(NSNotification*)notification;
136139
- (void)goToApplicationLifecycle:(nonnull NSString*)state;
137140
- (void)keyboardWillChangeFrame:(NSNotification*)notification;
141+
- (void)keyboardWillBeHidden:(NSNotification*)notification;
138142
- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
139143
- (void)ensureViewportMetricsIsCorrect;
140144
- (void)invalidateDisplayLink;
@@ -198,6 +202,30 @@ - (void)testkeyboardWillChangeFrameWillStartKeyboardAnimation {
198202
[self waitForExpectationsWithTimeout:5.0 handler:nil];
199203
}
200204

205+
- (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
206+
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
207+
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
208+
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
209+
nibName:nil
210+
bundle:nil];
211+
212+
FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
213+
CGRect keyboardFrame = CGRectMake(0, 0, 0, 0);
214+
BOOL isLocal = YES;
215+
NSNotification* fakeNotification = [NSNotification
216+
notificationWithName:@""
217+
object:nil
218+
userInfo:@{
219+
@"UIKeyboardFrameEndUserInfoKey" : [NSValue valueWithCGRect:keyboardFrame],
220+
@"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
221+
@"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
222+
}];
223+
224+
viewControllerMock.targetViewInsetBottom = 10;
225+
[viewControllerMock keyboardWillBeHidden:fakeNotification];
226+
XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
227+
}
228+
201229
- (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
202230
FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
203231
[mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];

0 commit comments

Comments
 (0)