Question

I have a project that contains a UIScrollView and many UITextField inside it.

For the first time I select a UITextField, UIKeyboardWillShowNotification is called, which is fine. But whenever I select new UITextField (THE KEYBOARD IS STILL THERE), UIKeyboardWillShowNotification is called again !!!, which is weird.

I also set a symbolic breakpoint for [UIResponder resignFirstResponder] and I see that it is hit before and after UIKeyboardWillShowNotification is called !!!

The other thing is that UIKeyboardWillHideNotification is only called when I hit the "Done" button on the keyboard

I'm sure to not call any resignFirstResponder, becomeFirstResponder, endEditing anywhere. (I mean not call wrongly)

What can cause this problem ?

Here is the stacktrace enter image description here

Was it helpful?

Solution 2

The problem is I set inputAccessoryView for the UITextField, and this cause UIKeyboardWillShowNotification being called again when new UITextField is selected

This article Working With Keyboard on iOS explains this well

Additional changes take place when we connect an external keyboard to the iPad. In this particular case, the notification behavior depends on the inputAccessoryView property of the control which was the reason for displaying the keyboard.

If inputAccessoryView is not present or its height is equal to 0 points, no keyboard notifications are sent. My guess is that this is because in this case, no visual changes take place in application. Otherwise, all notifications behave as expected – which means they are being sent as in the majority of cases when the keyboard is displayed or hidden in a normal (not undocked or split) state.

Whenever new UITextField is selected, the OS needs to compute the frame for the keyboard again, and the following notifications are posted

UIKeyboardWillChangeFrameNotification
UIKeyboardWillShowNotification
UIKeyboardDidChangeFrameNotification
UIKeyboardDidShowNotification

The same applies for when the TextField loses its first responder status

Note that using the same View for inputAccessoryView will cause UIKeyboardWillShowNotification only called once

OTHER TIPS

To workaround the problem, I used the following code to cancel the UIKeyboardWillShowNotification callback if the keyboard's frame is not changing.

func keyboardWillShow(notification: NSNotification) {

    let beginFrame = notification.userInfo![UIKeyboardFrameBeginUserInfoKey]!.CGRectValue()
    let endFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue()

    // Return early if the keyboard's frame isn't changing.
    guard CGRectEqualToRect(beginFrame, endFrame) == false else {
        return
    }

    ...
}

For Swift 3/4:

func keyboardWillShow(notification: Notification) {

    let userInfo = notification.userInfo!
    let beginFrameValue = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)!
    let beginFrame = beginFrameValue.cgRectValue
    let endFrameValue = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)!
    let endFrame = endFrameValue.cgRectValue

    if beginFrame.equalTo(endFrame) {
        return
    }

    // Do something with 'will show' event
    ...
}

In general I find that many things can cause spurious UIKeyboardWillShow and UIKeyboardWillHide notifications. My solution is to use a property to track whether the keyboard is already showing:

func keyboardShow(_ n:Notification) {
    if self.keyboardShowing {
        return
    }
    self.keyboardShowing = true
    // ... other stuff
}

func keyboardHide(_ n:Notification) {
    if !self.keyboardShowing {
        return
    }
    self.keyboardShowing = false
    // ... other stuff
}

Those guards block exactly the spurious notifications, and all is well after that. And the keyboardShowing property can be useful for other reasons, so that it is something worth tracking anyway.

The best approach is to Add notification & remove it once your purpose is solve.

like this .

- (void)viewWillAppear:(BOOL)animated
{
// register for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillShow)
                                             name:UIKeyboardWillShowNotification
                                           object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillHide)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];
}

Now write your code for movement of views & textField in keyboardWillShow & revert them back to position in keyboardWillHide methods.

Also remove the observers

- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:UIKeyboardWillShowNotification
                                              object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self
                                                name:UIKeyboardWillHideNotification
                                              object:nil];
}

You can also resign the responder when you press return key.

-(BOOL)textFieldShouldReturn:(UITextField *)textField {

    [_txtFieldEmail resignFirstResponder];
    [_txtFieldPassword resignFirstResponder];
    return YES;
}

That should solve your issue.

For those not using inputAccessoryView but are still having problems, it may be due to using sensitive (password) fields. See this Stack Overflow post and answer: keyboardWillShow in IOS8 with UIKeyboardWillShowNotification

I have struggled with this, after half a day of searching around and experimenting I think this is the shortest and most reliable code. It is a mix of a number of answers most of which I forget where I found (parts of which are mentioned here).

My problem was a WKWebView which (when the user changed fields) would spawn a load of WillShow, WillHide, etc notifications. Plus I had problem with the external keyboard which still has the onscreen touch bar thing.

This solution uses the same animation code to "Open" and "Close" the keyboard, it will also cope with external keyboard being attached and custom keyboard views.

First register for the UIKeyboardWillChangeFrameNotification.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillChangeFrame:)                                                 name:UIKeyboardWillChangeFrameNotification object:nil];

Then you simply need to map the changes to your view in however you do it (change a hight or bottom constraint constant).

- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
    NSDictionary *userInfo = notification.userInfo;
    CGRect keyboardEnd = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
    CGRect convertedEnd = [self.view convertRect:keyboardEnd fromView:nil];

    // Convert the Keyboard Animation to an Option, note the << 16 in the option
    UIViewAnimationCurve keyAnimation = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];

    // Change the Height or Y Contraint to the new value.
    self.keyboardHeightConstraint.constant = self.view.bounds.size.height - convertedEnd.origin.y;
    [UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
                          delay:0.0
                        options:keyAnimation << 16
                     animations:^{
                         [self.view layoutIfNeeded];
                     } completion:nil];

}

The Animation to Option conversion seems to work (I can only find examples of it being used and not how/why), however, I am not convinced it will stay that way so it may be wise to use a "stock" option. It seems that the Keyboard uses some none specified Animation.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top