I created a Swift 3 protocol to handle the keyboard appearance / disappearance
import UIKit
protocol KeyboardHandler: class {
var bottomConstraint: NSLayoutConstraint! { get set }
func keyboardWillShow(_ notification: Notification)
func keyboardWillHide(_ notification: Notification)
func startObservingKeyboardChanges()
func stopObservingKeyboardChanges()
}
extension KeyboardHandler where Self: UIViewController {
func startObservingKeyboardChanges() {
// NotificationCenter observers
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
// Deal with rotations
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
// Deal with keyboard change (emoji, numerical, etc.)
NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillShow(notification)
}
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in
self?.keyboardWillHide(notification)
}
}
func keyboardWillShow(_ notification: Notification) {
let verticalPadding: CGFloat = 20 // Padding between the bottom of the view and the top of the keyboard
guard let value = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardHeight = value.cgRectValue.height
// Here you could have more complex rules, like checking if the textField currently selected is actually covered by the keyboard, but that's out of this scope.
self.bottomConstraint.constant = keyboardHeight + verticalPadding
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func keyboardWillHide(_ notification: Notification) {
self.bottomConstraint.constant = 0
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.layoutIfNeeded()
})
}
func stopObservingKeyboardChanges() {
NotificationCenter.default.removeObserver(self)
}
}
Then, to implement it in a UIViewController, do the following:
- let the viewController conform to this protocol :
class FormMailVC: UIViewControlle, KeyboardHandler {
- start observing keyboard changes in viewWillAppear:
// MARK: - View controller life cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startObservingKeyboardChanges()
}
- stop observing keyboard changes in viewWillDisappear:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopObservingKeyboardChanges()
}
- create an IBOutlet for the bottom constraint from the storyboard:
// NSLayoutConstraints
@IBOutlet weak var bottomConstraint: NSLayoutConstraint!
(I recommend having all of your UI embedded inside a "contentView", and linking to this property the bottom constraint from this contentView to the bottom layout guide)
[![Content view bottom constraint][1]][1]
- change the constraint priority of the *top constraint* to 250 (low)
[![Content view top constraint][2]][2]
This is to let the whole content view slide upwards when the keyboard appears.
The priority must be lower than any other constraint priority in the subviews, including content hugging priorities / content compression resistance priorities.
- Make sure that your Autolayout has enough constraints to determine how the contentView should slide up.
You may have to add a "greater than equal" constraint for this:
[!["greater than equal" constraint][3]][3]
And here you go!
[![Without keyboard][4]][4]
[![With keyboard][5]][5]
[1]:
[2]:
[3]:
[4]:
[5]: