In my previous post, I suggested a way to make MVC great again using Generics, extensions, and Protocols. Today we will build on top of that to handle keyboard events :)
Update UIView layout and adapt to system keyboard event notifications.
We will introduce two new protocols and an extension.
KeyboardObserving
ProtocolConform to this protocol in a view controller to register to, unregister from keyboard events and pass notifications to its view
protocol KeyboardObserving: AnyObject
func keyboardWillShow(_ notification: Notification)
func keyboardDidShow(_ notification: Notification)
func keyboardWillHide(_ notification: Notification)
func keyboardDidHide(_ notification: Notification)
func keyboardWillChangeFrame(_ notification: Notification)
func keyboardDidChangeFrame(_ notification: Notification)
func registerForKeyboardEvents()
func unregisterFromKeyboardEvents()
}
We will extend the KeyboardObserving
protocol with a default implementation, this will make its methods optional as well
KeyboardControllable
ProtocolConform to this protocol in a view to respond to keyboard events
protocol KeyboardControllable: AnyObject {
func handleKeyboardWillShow(_ notification: Notification)
func handleKeyboardDidShow(_ notification: Notification)
func handleKeyboardWillHide(_ notification: Notification)
func handleKeyboardDidHide(_ notification: Notification)
func handleKeyboardWillChangeFrame(_ notification: Notification)
func handleKeyboardDidChangeFrame(_ notification: Notification)
}
Again we will extend the KeyboardControllable protocol with a default empty implementation, this will make its methods optional as well
Notification
ExtensionskeyboardSize
and keyboardAnimationDuration
properties will give us an easy way to get keyboard info from the system notification
extension Notification {
var keyboardSize: CGSize? {
return (userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size
}
var keyboardAnimationDuration: Double? {
return userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? Double
}
}
Let’s update the same example from my previous tutorial to push up text fields in LoginView
when the keyboard is presented and center them back when the keyboard is hidden.
LoginView.swift
// 1. conform to KeyboardControllable protocol
class LoginView: View, KeyboardControllable {
...
// 2. handle keyboard will show
func handleKeyboardWillShow(_ notification: Notification) {
// 3. get keyboard height from received notification
let keyboardSize = notification.keyboardSize
let keyboardHeight = keyboardSize?.height ?? 250
// 4. update stackView constrains based on keyboard height
stackView.snp.updateConstraints {
$0.bottom.equalToSuperview().inset(40 + keyboardHeight)
}
layoutIfNeeded()
}
// 5. handle keyboard will hide
func handleKeyboardWillHide(_ notification: Notification) {
stackView.snp.updateConstraints {
$0.bottom.equalToSuperview().inset(40)
}
layoutIfNeeded()
}
...
}
LoginViewController.swift
// 1. conform to KeyboardObserving protocol
class LoginViewController: ViewController<LoginView>, KeyboardObserving {
override func viewDidLoad() {
...
// 2. register for keyboard event notifications
registerForKeyboardEvents()
}
// 3. observe keyboardWillShow
func keyboardWillShow(_ notification: Notification) {
customView.handleKeyboardWillShow(notification)
}
// 4. observe keyboardWillHide
func keyboardWillHide(_ notification: Notification) {
customView.handleKeyboardWillHide(notification)
}
...
deinit {
// 5. unregister from keyboard notifications
// to avoid memory leaks
unregisterFromKeyboardEvents()
}
}
Use the power of protocols and generic types to avoid extension conflicts
Use generics, protocols, and extensions to get rid of massive view controllers