I have a view which is resize a constraint when the keyboard appear. So I have notifications when the keyboard appears and disappears.
The above behaviour occurs when the keyboard is already shown and I rotate the screen. Then the next actions occurs:
UIKeyboardWillHideNotification called
UIKeyboardWillShowNotification called (with the old height of the keyboard)
UIKeyboardWillShowNotification called (with the new height of the keyboard)
So, the updateView function receives first one height and later a different height. This results in a weird movements of the view adjusting twice the value.
override func viewDidLoad() {
super.viewDidLoad()
// Creates notification when keyboard appears and disappears
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, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
self.adjustingHeight(true, notification: notification)
}
func keyboardWillHide(notification: NSNotification) {
self.adjustingHeight(false, notification: notification)
}
private func adjustingHeight(show: Bool, notification: NSNotification) {
// Gets notification information in an dictionary
var userInfo = notification.userInfo!
// From information dictionary gets keyboard’s size
let keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
// Gets the time required for keyboard pop up animation
let animationDurarion = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
// Animation moving constraint at same speed of moving keyboard & change bottom constraint accordingly.
if show {
self.bottomConstraint.constant = (CGRectGetHeight(keyboardFrame) + self.bottomConstraintConstantDefault / 2)
} else {
self.bottomConstraint.constant = self.bottomConstraintConstantDefault
}
UIView.animateWithDuration(animationDurarion) {
self.view.layoutIfNeeded()
}
self.hideLogoIfSmall()
}
Finally I found the solution. When I get keyboardFrame, I was using UIKeyboardFrameBeginUserInfoKey which returns the frame of the keyboard before the animation begins. The correct way of do that is with UIKeyboardFrameEndUserInfoKeywhich returns the frame of the keyboard after the animation has completed.
let keyboardFrame: CGRect = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
Related
I'm trying to update the constraints of my view when the keyboard appears and disappears
I have notifications for keyboardWillShow and keyboardWillHide and corresponding functions to call them:
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name:UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil)
#objc private func keyboardWillShow(notification: NSNotification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardFrameEnd = keyboardFrame.cgRectValue
let convertedKeyboardFrameEnd = self.view.convert(keyboardFrameEnd, from: nil)
let intersection = convertedKeyboardFrameEnd.intersection(listView.frame)
if intersection.size.height > 0 {
self.listView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(0)
make.bottom.equalTo(-intersection.height)
}
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
}
#objc private func keyboardWillHide() {
self.listView.snp.makeConstraints { make in
make.edges.equalTo(0)
make.bottom.equalTo(0)
}
self.listView.setNeedsUpdateConstraints()
self.listView.updateConstraints()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
It seems that keyboardWillShow gets called and updates the size of my listView to be the visible size outside of the keyboard. However, keyboardWillHide gets called and it doesn't seem to update the bottom back to have value 0 so I end up with a case where my view is not resized back to its original size. Any ideas what might be wrong here?
In my application I need to get keyboard size for after move the others components to correctly position, now i'm using that code to get height
let info:NSDictionary = aNotification.userInfo! as NSDictionary
let kbSize:CGSize = (info.object(forKey: UIResponder.keyboardFrameEndUserInfoKey)! as AnyObject).cgRectValue.size
print(kbSize.height)
In the first time when keyboard is open the result is 260.0, but this value is size default, without the suggestions/passwords bar height, when I click out and click in the input again the result is 304.0 the value I want...
How I can get 304 since the first click in the input?
You should be listening for changes in the keyboard's size and adjusting the rest of your content that way, since, as you found out, iOS keyboards can change size. You should subscribe to UIResponder.keyboardWillChangeFrameNotification and/or UIResponder.keyboardDidChangeFrameNotification which are notifications which trigger when the keyboard's frame will, or did, change. Here's an example of it in use:
// Somewhere in set up code
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidChangeFrame),
name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
// Function elsewhere in your class
#objc func keyboardDidChangeFrame(_ notification: Notification) {
guard let kbSize = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey]
as? CGRect else {
return;
}
// Use kbRect as you initially did
}
Note that you can use either the Will or the Did notifications depending on how you want the layout change to look. You can also query both UIResponder.keyboardFrameBeginUserInfoKey UIResponder.keyboardFrameEndUserInfoKey to get the keyboard frame before and after the size change, which may be useful if you want to animate your layout along with the keyboard.
You can try this;
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChange(notification:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(UIResponder.keyboardWillShowNotification)
NotificationCenter.default.removeObserver(UIResponder.keyboardWillHideNotification)
NotificationCenter.default.removeObserver(UIResponder.keyboardWillChangeFrameNotification)
}
#objc func keyboardWillChange(notification: Notification) {
guard let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
if notification.name == UIResponder.keyboardWillChangeFrameNotification || notification.name == UIResponder.keyboardWillShowNotification {
view.frame.origin.y = -keyboardSize.height
} else {
view.frame.origin.y = 0
}
}
}
HERE is the Github gist
In my app, I wanted a notification for UIResponder.keyboardWillShowNotification in order to update the y position of my text field. It was working prior to iOS 12; now, it is not called in one of my view controllers (it works for other ones).
Here is my code to do this:
#objc func keyboardWillShow(_ notification: Notification) {
print("keyboard will show 2")
guard let frameValue: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else {
return
}
let keyboardFrame = frameValue.cgRectValue
UIView.animate(withDuration: animationTime) {
self.addViewBottomConstraint.constant = keyboardFrame.size.height
self.view.layoutIfNeeded()
print("Bottom contraint height = \(self.addViewBottomConstraint.constant)")
}
}
#objc func keyboardWillHide(_ notification: Notification) {
UIView.animate(withDuration: animationTime) {
self.addViewBottomConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
}
Here, "keyboard will show 2" is not printed, but it is printed for other view controllers with the same notifications. Is there anything new in iOS 12 that caused this? Otherwise, is there a particular reason why it is not being called?
This can be related to simulator setup, see menu "Hardware > Keyboard > Connect Keyboard Hardware". If this option is ON, you will get UIKeyboardWillHideNotification, but never UIKeyboardWillShowNotification.
I create notifications when the keyboard appears and disappears.
override func viewDidLoad() {
super.viewDidLoad()
// Creates notification when keyboard appears and disappears
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
self.adjustingHeight(true, notification: notification)
}
func keyboardWillHide(notification: NSNotification) {
self.adjustingHeight(false, notification: notification)
}
private func adjustingHeight(show: Bool, notification: NSNotification) {
// Gets notification information in an dictionary
var userInfo = notification.userInfo!
// From information dictionary gets keyboard’s size
let keyboardFrame:CGRect = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
// Gets the time required for keyboard pop up animation
let animationDurarion = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
// Animation moving constraint at same speed of moving keyboard & change bottom constraint accordingly.
if !(show && self.bottomConstraint.constant != self.bottomConstraintConstantDefault) {
if show {
self.bottomConstraint.constant = (CGRectGetHeight(keyboardFrame) + self.bottomConstraintConstantDefault / 2)
} else {
self.bottomConstraint.constant = self.bottomConstraintConstantDefault
}
UIView.animateWithDuration(animationDurarion) {
self.view.layoutIfNeeded()
}
}
self.hideLogoIfSmall()
}
The strange behaviour occurs when the keyboard is already shown and I rotate the screen. Then the next actions occurs:
UIKeyboardWillHideNotification called
UIKeyboardWillShowNotification called (with the old height of the keyboard)
UIKeyboardWillShowNotification called (with the new height of the keyboard)
The result of this is that my view is not updated properly because the first time UIKeyboardWillShowNotification is called I receive a different height of the keyboard than the second time.
Finally I found the solution. When I get keyboardFrame, I was using UIKeyboardFrameBeginUserInfoKey which returns the frame of the keyboard before the animation begins. The correct way of do that is with UIKeyboardFrameEndUserInfoKeywhich returns the frame of the keyboard after the animation has completed.
let keyboardFrame: CGRect = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
Now UIKeyboardWillShowNotification is called twice but returns the same height of the keyboard in both calls.
I am adding a keyboard observer to adjust the view height when the keyboard shows. But when the text field is at the top of the view and the keyboard shows, the text field goes up even more. Can someone please guide me on how to fix this properly because after performing lots of searches, they all suggest the same thing. I am using iOS 9 on an iPhone 5.
Here is my code. In viewDidLoad():
//SET OBSERVER WHEN KEYBOARD IS SHOWN
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name:UIKeyboardWillShowNotification, object: self.view.window)
//SET OBSERVER WHEN KEYBOARD IS HIDDEN
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name:UIKeyboardWillHideNotification, object: self.view.window)
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: self.view.window)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: self.view.window)
}
//HANDLING KEYBOARD APPEARING AND DISAPPEARING
func keyboardWillHide(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
self.view.frame.origin.y += keyboardSize.height
}
func keyboardWillShow(sender: NSNotification) {
let userInfo: [NSObject : AnyObject] = sender.userInfo!
let keyboardSize: CGSize = userInfo[UIKeyboardFrameBeginUserInfoKey]!.CGRectValue.size
let offset: CGSize = userInfo[UIKeyboardFrameEndUserInfoKey]!.CGRectValue.size
print(("\(keyboardSize.height)---\(offset.height)")) **//this is always showing the values 216.0 for both**
if keyboardSize.height == offset.height {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
})
} else {
UIView.animateWithDuration(0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height - offset.height
})
}
}
You're taking a complicated approach to what isn't a very difficult problem. Please remember that phones come in a variety of sizes, so trying to move a view around might work great for one device (e.g. iPhone 5) but be a poor experience on others (e.g. iPhone 4, iPhone 6s).
The solution you're looking for is to place your controls inside a UIScrollView. You then don't move anything when the keyboard shows or hides – you just adjust the content inset of your scroll view, and let iOS do the rest.
The keyboard being shown and hidden is complicated by the fact that there are also hardware keyboards that can be connected and unplugged at will; in my (extremely extensive!) testing, I've managed to pull together code that seems to behave properly in all conditions, no matter what happens with the keyboard. I've tried to trim it down smaller and have always found an edge case where it stopped working, so I've settled with this fixed approach and it works great. If you're able to use a UIScrollView, this exact approach will work for you too.
Step 1: Register for the right notifications. Specifically, put this into viewDidLoad():
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: "adjustForKeyboard:", name: UIKeyboardWillChangeFrameNotification, object: nil)
Step 2: Add this method to your view controller:
func adjustForKeyboard(notification: NSNotification) {
let userInfo = notification.userInfo!
let keyboardScreenEndFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardViewEndFrame = view.convertRect(keyboardScreenEndFrame, fromView: view.window)
if notification.name == UIKeyboardWillHideNotification {
yourScrollView.contentInset = UIEdgeInsetsZero
} else {
yourScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height, right: 0)
}
yourScrollView.scrollIndicatorInsets = yourScrollView.contentInset
}
Step 3: Anyone using a UITextView should also add these two lines to the end of that method so the user doesn't lose their place:
let selectedRange = yourScrollView.selectedRange
yourScrollView.scrollRangeToVisible(selectedRange)
…although presumably in that case it would be yourTextView or similar.