83

I was using this code to determine what is the size of the keyboard :

- (void)keyboardWillChange:(NSNotification *)notification {
    NSDictionary* keyboardInfo = [notification userInfo];
    NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
    CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];

}

I'm running this in the simulator.

The problem is since iOS 8 this will not give the correct value, if the keyboard suggestions is up or if I push them down I get different (not correct) values.

How can I get the exact size of the keyboard including the keyboard suggestions?

Eli Braginskiy
  • 2,867
  • 5
  • 31
  • 46

10 Answers10

120

With the introduction of custom keyboards in iOS, this problem becomes a tad more complex.

In short, the UIKeyboardWillShowNotification can get called multiple times by custom keyboard implementations:

  1. When the Apple system keyboard is opened (in portrait)
    • UIKeyboardWillShowNotification is sent with a keyboard height of 224
  2. When the Swype keyboard is opened (in portrait):
    • UIKeyboardWillShowNotification is sent with a keyboard height of 0
    • UIKeyboardWillShowNotification is sent with a keyboard height of 216
    • UIKeyboardWillShowNotification is sent with a keyboard height of 256
  3. When the SwiftKey keyboard is opened (in portrait):
    • UIKeyboardWillShowNotification is sent with a keyboard height of 0
    • UIKeyboardWillShowNotification is sent with a keyboard height of 216
    • UIKeyboardWillShowNotification is sent with a keyboard height of 259

In order to handle these scenarios properly in one code-line, you need to:

Register observers against the UIKeyboardWillShowNotification and UIKeyboardWillHideNotification notifications:

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

Create a global variable to track the current keyboard height:

CGFloat _currentKeyboardHeight = 0.0f;

Implement keyboardWillShow to react to the current change in keyboard height:

- (void)keyboardWillShow:(NSNotification*)notification {
   NSDictionary *info = [notification userInfo];
   CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
   CGFloat deltaHeight = kbSize.height - _currentKeyboardHeight; 
   // Write code to adjust views accordingly using deltaHeight
   _currentKeyboardHeight = kbSize.height;
}

NOTE: You may wish to animate the offsetting of views. The info dictionary contains a value keyed by UIKeyboardAnimationDurationUserInfoKey. This value can be used to animate your changes at the same speed as the keyboard being displayed.

Implement keyboardWillHide to the reset _currentKeyboardHeight and react to the keyboard being dismissed:

- (void)keyboardWillHide:(NSNotification*)notification {
   NSDictionary *info = [notification userInfo];
   CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
   // Write code to adjust views accordingly using kbSize.height
   _currentKeyboardHeight = 0.0f;
}
kevinl
  • 4,194
  • 6
  • 37
  • 55
dgangsta
  • 1,315
  • 2
  • 7
  • 4
  • dgangsta, your research is interesting, but the problem from the question is just about getting FrameBegin instead of FrameEnd properties. According to your answer, had you consider using UIViewAnimationOptionBeginFromCurrentState flag to the view animations method instead of deltas? – MikeR Nov 11 '14 at 13:31
  • Instead 259 for SwiftKey(v 1.2.3) height, I got 271 in iPhone6+ with iOS8.1.3. – Hemang Mar 25 '15 at 06:14
  • 2
    Is it still a working solution ? When using SwiftKey I get an height of 44 instead of 259. – KIDdAe Apr 22 '15 at 12:41
  • Crazy, this helped me, but I needed the height of the keyboard after it was already showing, so I observe keyboardDidShow: instead. Why do these custom keyboards trigger 3 notifications while Apple's just triggers one? Seems inconsistent. – Liron Yahdav Feb 04 '16 at 03:11
  • If you're having issues with iPhone in landscape mode like I was, it's because the frame END key has the *wrong origin* (at least for normal iOS 10 keyboard). `KEYBOARD BEGIN RECT: (0.0, 375.0, 667.0, 162.0) ... KEYBOARD END RECT: (0.0, 213.0, 667.0, 162.0)` – xaphod Jun 20 '17 at 14:21
99

Use

NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
souvickcse
  • 7,742
  • 5
  • 37
  • 64
18

I also had this issue, until I came across this StackOverflow article:

Convert UIKeyboardFrameEndUserInfoKey

This shows you how to use the convertRect function, to convert the keyboard's size into something usable, but on the screen orientation.

NSDictionary* d = [notification userInfo];
CGRect r = [d[UIKeyboardFrameEndUserInfoKey] CGRectValue];
r = [myView convertRect:r fromView:nil];

Previously, I had an iPad app which used UIKeyboardFrameEndUserInfoKey but didn't use convertRect, and it worked fine.

But with iOS 8, it no longer worked properly. Suddenly, it would report that my keyboard, running on an iPad in landscape mode, was 1024 pixels high.

So now, with iOS 8, it's essential that you use this convertRect function.

Community
  • 1
  • 1
Mike Gledhill
  • 27,846
  • 7
  • 149
  • 159
  • Pre-iOS8 the keyboard rect was always in portrait oriented screen coordinates. I had added code to manually swap the height and width when in landscape mode, but that broke on iOS 8. where keyboard rect orientation matches view orientation. The convertRect solution gives correct result for iOS 7 or iOS 8. – user1055568 Feb 02 '15 at 19:32
7

The similar to dgangsta's solution written in Swift 2.0:

override func viewDidLoad() {
    super.viewDidLoad()
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
    NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}

deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

func keyboardWillShow(notification: NSNotification) {
    guard let kbSizeValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
    guard let kbDurationNumber = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber else { return }
    animateToKeyboardHeight(kbSizeValue.CGRectValue().height, duration: kbDurationNumber.doubleValue)
}

func keyboardWillHide(notification: NSNotification) {
    guard let kbDurationNumber = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber else { return }
    animateToKeyboardHeight(0, duration: kbDurationNumber.doubleValue)
}

func animateToKeyboardHeight(kbHeight: CGFloat, duration: Double) {
    // your custom code here
}
Community
  • 1
  • 1
Avt
  • 16,927
  • 4
  • 52
  • 72
5

I make extension for UIViewController

extension UIViewController {
    func keyboardWillChangeFrameNotification(notification: NSNotification, scrollBottomConstant: NSLayoutConstraint) {
        let duration = notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
        let curve = notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber
        let keyboardBeginFrame = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
        let keyboardEndFrame = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()

        let screenHeight = UIScreen.mainScreen().bounds.height
        let isBeginOrEnd = keyboardBeginFrame.origin.y == screenHeight || keyboardEndFrame.origin.y == screenHeight
        let heightOffset = keyboardBeginFrame.origin.y - keyboardEndFrame.origin.y - (isBeginOrEnd ? bottomLayoutGuide.length : 0)

        UIView.animateWithDuration(duration.doubleValue,
            delay: 0,
            options: UIViewAnimationOptions(rawValue: UInt(curve.integerValue << 16)),
            animations: { () in
                scrollBottomConstant.constant = scrollBottomConstant.constant + heightOffset
                self.view.layoutIfNeeded()
            },
            completion: nil
        )
    }
}

You can use like this :

override func viewDidLoad() {
    super.viewDidLoad()

    NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillChangeFrameNotification:", name: UIKeyboardWillChangeFrameNotification, object: nil)
}

...

deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self)
}

func keyboardWillChangeFrameNotification(notification: NSNotification) {
    self.keyboardWillChangeFrameNotification(notification, scrollBottomConstant: inputContainerBottom)
    // Write more to here if you want.
}
Wanbok Choi
  • 5,432
  • 1
  • 21
  • 26
5

There are times when devs need to know the keyboard height before it's actually shown, allowing them pre-layout the interface appropriately.

If that's the case, here's an inclusive spec:

enter image description here

This includes the quick type bar at the top, since that is on by default in all current versions of iOS.

Here is the swift 3 notification setup I used to test this, if anyone needs it:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: .UIKeyboardWillShow, object: nil)
}

func keyboardWillShow(notification: NSNotification) {
    guard let keyboardSize = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
    print("\(keyboardSize)")
}
Travis M.
  • 10,930
  • 1
  • 56
  • 72
1

Only one string for swift:

let keyboardSize = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue().size

UIKeyboardFrameEndUserInfoKey always stores NSValue, so no need to check it.

Petr Syrov
  • 14,689
  • 3
  • 20
  • 30
0

I noticed an issue showing when switching between default keyboard and a custom (UIPickerView) keyboard - the custom keyboard would show a 253 height instead of 162, after switching from the default keyboard.

What worked in this case was setting autocorrectionType = UITextAutocorrectionTypeNo; for the input field with the custom keyboard.

The issue only occured in iOS 8 (tested on simulator only). It doesn't occur in iOS 9 (simulator or device).

alex-i
  • 5,406
  • 2
  • 36
  • 56
0

In Swift, not in one line...

self.keyboardDidShowObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { (notification) in
        if let userInfo = notification.userInfo, let keyboardFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue {
            let keyboardRect = keyboardFrameValue.CGRectValue()
            // keyboardRect.height gives the height of the keyboard
            // your additional code here...
        }
    })
Murray Sagal
  • 8,454
  • 4
  • 47
  • 48
0
[notificationCenter addObserverForName:UIKeyboardWillChangeFrameNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {

    float keyboardHeight = [[note.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;

}];
Leonardo Cavalcante
  • 1,274
  • 1
  • 16
  • 26