can't get correct value of keyboard height in iOS8

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?

64489 次浏览

Use

NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];

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;
}

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.

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
}

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.
}

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).

Only one string for swift:

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

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

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...
}
})
[notificationCenter addObserverForName:UIKeyboardWillChangeFrameNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {


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


}];

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)")
}