215
votes

Note:

See accepted answer (not top voted one) for solution as of iOS 4.3.

This question is about a behavior discovered in the iPad keyboard, where it refuses to be dismissed if shown in a modal dialog with a navigation controller.

Basically, if I present the navigation controller with the following line as below:

navigationController.modalPresentationStyle = UIModalPresentationFormSheet;

The keyboard refuses to be dismissed. If I comment out this line, the keyboard goes away fine.

...

I've got two textFields, username and password; username has a Next button and password has a Done button. The keyboard won't go away if I present this in a modal navigation controller.

WORKS

broken *b = [[broken alloc] initWithNibName:@"broken" bundle:nil];
[self.view addSubview:b.view];

DOES NOT WORK

broken *b = [[broken alloc] initWithNibName:@"broken" bundle:nil];
UINavigationController *navigationController = 
[[UINavigationController alloc]
 initWithRootViewController:b];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
navigationController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:navigationController animated:YES];
[navigationController release];
[b release];

If I remove the navigation controller part and present 'b' as a modal view controller by itself, it works. Is the navigation controller the problem?

WORKS

broken *b = [[broken alloc] initWithNibName:@"broken" bundle:nil];
b.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:b animated:YES];
[b release];

WORKS

broken *b = [[broken alloc] initWithNibName:@"broken" bundle:nil];
UINavigationController *navigationController = 
    [[UINavigationController alloc]
         initWithRootViewController:b];
[self presentModalViewController:navigationController animated:YES];
[navigationController release];
[b release];
13
The following SO question seems to be having the same problem, but there are no answers: stackoverflow.com/questions/3019709/…Kalle
+1 Thank you for your great explanation. But where do I have to put that method? It seems not working where I create the code for presenting the model controller...Lorenzo B
It has to be in the modal view controller class itself.Kalle
Thanks. I see. I solved putting it in a category for UINavigationController class. Cheers.Lorenzo B
I'm so indebted to you for this question. I was surprised that resignFirstResponder was executing but the keyboard still being shown. My scenario (presentationFormSheet with navig contrllr) is exactly the same as yours. Thanks a ton!!sErVerdevIL

13 Answers

116
votes

In the view controller that is presented modally, just override disablesAutomaticKeyboardDismissal to return NO:

- (BOOL)disablesAutomaticKeyboardDismissal {
    return NO;
}
172
votes

This has been classified as "works as intended" by Apple engineers. I filed a bug for this a while back. Their reasoning is that the user is often going to be entering data in a modal form so they are trying to be "helpful" and keep the keyboard visible where ordinarily various transitions within the modal view can cause the keyboard to show/hide repeatedly.

edit: here is the response of an Apple engineer on the developer forums:

Was your view by any chance presented with the UIModalPresentationFormSheet style? To avoid frequent in-and-out animations, the keyboard will sometimes remain on-screen even when there is no first responder. This is not a bug.

This is giving a lot of people problems (myself included) but at the moment there doesn't seem to be a way to work around it.

UPDATE:

In iOS 4.3 and later, you can now implement `-disablesAutomaticKeyboardDismissal' on your view controller to return NO:

- (BOOL)disablesAutomaticKeyboardDismissal {
    return NO;
}

This fixes the issue.

150
votes

Be careful if you are displaying the modal with a UINavigationController. You then have to set the disablesAutomaticKeyboardDismissal on the navigation controller and not on the view controller. You can easily do this with categories.

File: UINavigationController+KeyboardDismiss.h

#import <Foundation/Foundation.h>

@interface UINavigationController (KeyboardDismiss)

- (BOOL)disablesAutomaticKeyboardDismissal;

@end

File: UINavigationController+KeyboardDismiss.m

#import "UINavigationController+KeyboardDismiss.h"

@implementation UINavigationController(KeyboardDismiss)

- (BOOL)disablesAutomaticKeyboardDismissal
{
    return NO;
}

@end

Do not forget to import the category in the file where you use the UINavigationController.

51
votes

I solved this by using the UIModalPresentationPageSheet presentation style and resizing it immediately after I present it. Like so:

viewController.modalPresentationStyle = UIModalPresentationPageSheet;
viewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:viewController animated:YES];
viewController.view.superview.autoresizingMask = 
    UIViewAutoresizingFlexibleTopMargin | 
    UIViewAutoresizingFlexibleBottomMargin;    
viewController.view.superview.frame = CGRectMake(
    viewController.view.superview.frame.origin.x,
    viewController.view.superview.frame.origin.y,
    540.0f,
    529.0f
);
viewController.view.superview.center = self.view.center;
[viewController release];
1
votes

If you toggle a different modal display you can get the keyboard to disappear. It's not pretty and it doesn't animate down, but you can get it to go away.

It'd be great if there was a fix, but for now this works. You can wedge it in a category on UIViewController and call it when you want the keyboard gone:

@interface _TempUIVC : UIViewController
@end

@implementation _TempUIVC
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return YES;
}
@end

@implementation UIViewController (Helpers)

- (void)_dismissModalViewController {
    [self dismissModalViewControllerAnimated:NO];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
    [self release];
}

- (void)forceKeyboardDismissUsingModalToggle:(BOOL)animated {
    [self retain];
    _TempUIVC *tuivc = [[_TempUIVC alloc] init];
    tuivc.modalPresentationStyle = UIModalPresentationCurrentContext;
    [self presentModalViewController:tuivc animated:animated];
    if (animated) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_dismissModalViewController) name:UIKeyboardDidHideNotification object:nil];
    } else
        [self _dismissModalViewController];
    [tuivc release];
}

@end

Be careful with this though as you viewDidAppear / viewDidDisappear and all those methods get called. Like I said, it's not pretty, but does work.

-Adam

1
votes

You could also work around this in a universal app by simply checking the idiom and if it's an iPad, don't pop up the keyboard automatically at all and let the user tap whatever they want to edit.

May not be the nicest solution but it's very straightforward and doesn't need any fancy hacks that will break with the next major iOS release :)

1
votes

Put this code in your viewWillDisappear: method of current controller is another way to fix this:

Class UIKeyboardImpl = NSClassFromString(@"UIKeyboardImpl");
id activeInstance = [UIKeyboardImpl performSelector:@selector(activeInstance)];
[activeInstance performSelector:@selector(dismissKeyboard)];
1
votes

I found that disablesAutomaticKeyboardDismissal and adding a disablesAutomaticKeyboardDismissal function didn't work for my UITextField in a modal dialog.

The onscreen keyboard just wouldn't go away.

My solution was to disable all text-input controls in my dialog, then re-enable the relevant ones a fraction of a second later.

It seems as though when iOS sees that none of the UITextField controls are enabled, then it does get rid of the keyboard.

0
votes

I'm sure you have looked at this, but you are sure that your controller class is properly hooked up as the UITextField delegate, right?

0
votes

maybe don't return NO, but YES. So it can go away.

And you have a textFieldShouldEndEditing returning YES as well?

And why are you firing [nextResponder becomeFirstResponder]?! sorry i see now

I also have a number of UITextViews which all have their "editable" property set to FALSE.

May we assume none of them, by any chance, has a tag value of secondField.tag+1? If so, you're telling them to become first responder, instead of resigning the first responder. Maybe put some NSLog() in that if structure.

0
votes

For those having trouble with UINavigationController, see my answer to a similar question here: https://stackoverflow.com/a/10507689/321785

Edit: I consider this an improvement to Miha Hribar's solution (since the decision is taking place where it should), and as opposed to Pascal's comment regarding a category on UIViewController

0
votes

may be not a perfect solution ,but works
[self.view endEditing:YES];
from wherever your button or gesture is implemented to present modal

0
votes
Swift 4.1:
extension UINavigationController {
   override open var disablesAutomaticKeyboardDismissal: Bool {
      return false
   }
}