iosuikeyboardresignfirstresponderbecomefirstresponderinputview

becomeFirstResponder is triggering UIKeyboardDidHideNotification


One goal of my app is to manage multiple custom input views (from nibs) along with the regular system keyboard. One or the other will always be present. (In the example code I am only managing a single nib + the system keyboard).

When swapping out the keyboards, I want to preserve the slide-in/slide-out effect you see with the system keyboard. To get the sliding animation for both the custom keyboard as well as the system keyboard I resignFirstResponder from the textfield and wait until the keyboard is hidden. Then I execute a becomeFirstResponder to bring in the new keyboard. I use the UIKeyboardDidHideNotification as a trigger to fire becomeFirstResponder and load the new keyboard.

The problem I see is that the UIKeyboardDidHideNotification is firing twice... once when executing resignFirstResponder (as expected) and again when becomeFirstResponder is executed. I suspect I can put in some state to detect the second notification, but I would like to understand, why becomeFirstResponder is causing it to fire in the first place?

#import "DemoViewController.h"

@interface DemoViewController ()
@property (strong, nonatomic) IBOutlet UITextField *dummyTextField;
@end

@implementation DemoViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [[UITextField appearance] setKeyboardAppearance:UIKeyboardAppearanceDark];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];

    [self showNormalKeyboard];
    [_dummyTextField becomeFirstResponder];
}

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

-(void)keyboardDidHide:(NSNotification*) notification {
    NSLog(@"Keyboard hidden: %@", notification);
    // Executing this next line causes the notification to fire again
    [_dummyTextField becomeFirstResponder];
}

-(void)showAlternateKeyboard{
    _dummyTextField.inputView = [[[NSBundle mainBundle] loadNibNamed:@"AlternateKeyboardView" owner:self options:nil] lastObject];
    [_dummyTextField resignFirstResponder];
}

-(void)showNormalKeyboard{
    _dummyTextField.inputView = nil;
    [_dummyTextField resignFirstResponder];
}

// This is a button on the DemoViewController screen
// that is always present.
- (IBAction)mainButtonPressed:(UIButton *)sender {
    NSLog(@"Main Screen Button Pressed!");
}

// This is a button from the Custom Input View xib
// but hardwired directly into here
- (IBAction)alternateKeyboardButtonPressed:(UIButton *)sender {
    NSLog(@"Alternate Keyboard Button Pressed!");
}

// This is a UISwitch on the DemoViewController storyboard
// that selects between the custom keyboard and the regular
// system keyboard
- (IBAction)keyboardSelectChanged:(UISwitch *)sender {
    if (sender.on){
        [self showAlternateKeyboard];
    } else {
        [self showNormalKeyboard];
    }
}

@end

Solution

  • My guess is that you are calling becomeFirstResponder before the previous field has fully finished resigning first responder. Just to see if it helps, try adding some delayed performance:

    -(void)keyboardDidHide:(NSNotification*) notification {
        NSLog(@"Keyboard hidden: %@", notification);
        dispatch_async(dispatch_get_main_queue(), ^{
            [_dummyTextField becomeFirstResponder];
        };
    }