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?
Solution 1:
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:
- When the Apple system keyboard is opened (in portrait)
- UIKeyboardWillShowNotification is sent with a keyboard height of 224
- 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
- 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;
}
Solution 2:
Use
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
Solution 3:
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.
Solution 4:
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
}
Solution 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.
}