16

I am trying to move a UITextView above the keyboard whenever the keyboard appears/changes. Let's say I have the English keyboard displaying and then switch directly to the Chinese keyboard (which is taller than the standard English keyboard). In this scenario my text view always appears too high (to the naked eye, it looks like the text view is incorrectly offset by the size of the Chinese keyboard "input accessory view". I'd post an image but lack the reputation to do so :) ). I am adjusting my text view position when my app receives a UIKeyboardDidShowNotification (using UIKeyboardFrameEndUserInfoKey to get the height), and after some investigation UIKeyboardDidShowNotification is called multiple times, oftentimes with the incorrect keyboard dimensions (I've NSLogged the userInfo dictionary). I register for my keyboard notifications in ViewWillAppear and unregister in ViewWillDisappear. I am unable to determine what might be causing this notification to fire multiple times; my understanding is that this notification should only be fired one time, right after the keyboard finishes displaying. An additional note: I've NSLogged 'self' in the method that responds to UIKeyboardDidShowNotification and it is in fact always the same View Controller Object.

Even with this notification firing multiple times, however, I still do not understand why the keyboard height would be different for some of these notifications. One of the notifications always has the correct height, but when it is not the last notification fired the text view ends up in the wrong spot. Any insight on how to further troubleshoot would be much appreciated!

EDIT: The more I test the more it seems to be a problem with the Chinese keyboard specifically. Whenever I switch the keyboard from english to chinese I get three UIKeyboardWillShowNotifications:

2014-12-24 22:49:29.385 Example[1055:421943] info dictionary: {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = 0;
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 252}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 460}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 442}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 352}, {320, 216}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 316}, {320, 252}}";
}
2014-12-24 22:49:29.408 Example[1055:421943] info dictionary: {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = 0;
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 442}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 460}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 316}, {320, 252}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 352}, {320, 216}}";
}
2014-12-24 22:49:29.420 Example[1055:421943] info dictionary: {
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = 0;
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 288}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 442}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 424}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 316}, {320, 252}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 280}, {320, 288}}";
}

The first one has the correct end height: 252. However, the next two are incorrect at 216 and 288. This happens reliably.

Here are a couple of snippets to demonstrate how I am managing subscriptions to notifications:

-(void)viewWillAppear:(BOOL)animated {

       [super viewWillAppear:animated];


       [self registerForKeyboardNotifications];


}

-(void)viewWillDisappear:(BOOL)animated {
       [super viewWillDisappear:animated];

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

}

- (void)registerForKeyboardNotifications {



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

      [[NSNotificationCenter defaultCenter] addObserver:self      
                                         selector:@selector(keyboardDidShow:)                                       
                                             name:UIKeyboardDidShowNotification object:nil];
}
Benny B
  • 315
  • 1
  • 3
  • 12
  • I experienced this also setting a custom inputView (an UIPickerView) of the text field. – MrTJ Jan 15 '15 at 11:47
  • 2
    I have identified the problem and a workaround. In my method that gets called in response to a UIKeyboardWillShowNotification I call [self.view layoutSubviews] within an animation block. When I animate layoutSubviews, additional UIKeyboardWillShowNotifications are triggered with the strange values posted in my question. If I call [self.view layoutSubviews] outside of an animation block, the notification is only triggered once and the value is always correct. So, without animation this works fine but I still do not understand why it does not work correctly with animation. – Benny B Jan 16 '15 at 01:59
  • I'm facing the same problem! In my test, English/Japanese keyboard are no problem, only Chinese one sometimes the notification get called multiple times and with wrong dimensions. – Alston Feb 12 '15 at 06:20
  • I gave up trying to get it working with animation. But it does work consistently without, so I'm relatively satisfied with the solution – Benny B Feb 12 '15 at 06:36
  • Found solution [here](https://developer.apple.com/library/prerelease/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html) – Alston Feb 12 '15 at 07:36
  • @Alston What part of those docs solves this issue? – Evan R May 09 '16 at 19:46
  • Can you post `keyboardDidHide()` & `keyboardDidShow()` code? – Anirudha Mahale Jul 19 '18 at 05:35

4 Answers4

8

If you're using the Cmd+K shortcut on the simulator to show/hide the keyboard, it may get called multiple times since that doesn't resign the textfield as the first responder.

If you instead use the keyboard's Return button then it will do the right thing and resign it.

iwasrobbed
  • 45,197
  • 20
  • 140
  • 190
  • 3
    Can't believe someone voted down this answer. Indeed with Cmd+K it is called twice. Thanks! – Borzh Jun 25 '16 at 01:23
4

The primary reason is that the your notification is invoked multiple times. For example, in swift, if you had the NSNotificationCenter.defaultCenter().addObserver(xx"keyboardWillShow"xx) inside viewWillAppear method and if you are going back and forth between the view then it will result in having multiple observer of the same "keyboardWillShow". Instead you should consider moving the addObserver call to "viewDidLoad()" method. Alternatively you can register/deregister observer when the view appears/disappears.

iwasrobbed
  • 45,197
  • 20
  • 140
  • 190
Big D
  • 357
  • 3
  • 6
0
- (void)keyboardDidAppear:(NSNotification *)note {
    CGSize keyboardSize = [note.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize.height, 0.0);
    textView.contentInset = contentInsets;
    textView.scrollIndicatorInsets = contentInsets;
}

- (void)keyboardWillBeHidden:(NSNotification *)note {
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    textView.contentInset = contentInsets;
    textView.scrollIndicatorInsets = contentInsets;
}

The solution is from Apple's official solution here like 曹 said.

Stunner
  • 11,108
  • 12
  • 79
  • 138
DianeZhou
  • 171
  • 1
  • 8
0

In my case, I register for my keyboard notifications in ViewWillAppear and unregister in ViewWillDisappear too. But, It will cause the UIKeyboardDidShowNotification handler be fired multiple times. Seem like the unregister method do not work, so, every time the view appears, the counts of Observer for UIKeyboardDidShowNotification plus 1. Then, you touch inside UITextField, multiple Observer be notified, handler be fired again and again.

So, you must register for keyboard notifications in ViewDidLoad and don't unregister. Just like the Apple mentioned in this page,

// Call this method somewhere in your view controller setup code.

I think the 'setup' means ViewDidLoad.And In the Handling the keyboard notifications code list,no ViewWillDisappear.

Here's my handler for keyboard notify, and it work.

   func keyboardWillShow(notification: NSNotification) {
    let userInfo = notification.userInfo!
    let keyboardHeight = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue().height
    debugPrint("Before",NotifyView.frame)
    NotifyView.frame.offsetInPlace(dx: 0, dy: -keyboardHeight)
    debugPrint("After",NotifyView.frame)
}
func keyboardWillHide(notification: NSNotification) {//Do Nothing
}
henry ren
  • 425
  • 3
  • 7