Reputation: 48085
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
Upvotes: 21
Views: 20347
Reputation: 832
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
Upvotes: 1
Reputation: 12053
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
...
}
Upvotes: 22
Reputation: 2272
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.
Upvotes: 0
Reputation: 535121
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.
Upvotes: 5
Reputation: 48085
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
Upvotes: 16
Reputation: 5667
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.
Upvotes: 1