Reputation: 248
I tried to create a universal mechanism that could react to keyboard showing and hiding in iOS. I came up with simple protocol and its extension that doesn't contain real animations but pulls them from object that adopts this protocol:
import UIKit
protocol KeyboardAnimatable {
func keyboardWillShowAnimation() -> (() -> Void)?
func keyboardWillHideAnimation() -> (() -> Void)?
}
extension KeyboardAnimatable where Self: UIViewController {
func setupKeyboardNotifcationListeners() {
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillHide:"), name: UIKeyboardWillHideNotification, object: nil)
}
func removeKeyboardNotificationListeners() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillShow(notification: NSNotification) {
let userInfo = notification.userInfo as! Dictionary<String, AnyObject>
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
let animationCurve = userInfo[UIKeyboardAnimationCurveUserInfoKey]!.intValue
let curveAnimationOption = UIViewAnimationOptions(rawValue: UInt(animationCurve))
let options: UIViewAnimationOptions = [.BeginFromCurrentState, curveAnimationOption]
if let animation = keyboardWillShowAnimation() {
UIView.animateWithDuration(animationDuration, delay: 0, options: options, animations: animation, completion: nil)
}
}
func keyboardWillHide(notification: NSNotification) {
let userInfo = notification.userInfo as! Dictionary<String, AnyObject>
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSTimeInterval
let animationCurve = userInfo[UIKeyboardAnimationCurveUserInfoKey]!.intValue
let curveAnimationOption = UIViewAnimationOptions(rawValue: UInt(animationCurve))
let options: UIViewAnimationOptions = [.BeginFromCurrentState, curveAnimationOption]
if let animation = keyboardWillHideAnimation() {
UIView.animateWithDuration(animationDuration, delay: 0, options: options, animations: animation, completion: nil)
}
}
}
I explicitly use where
clause in extension KeyboardAnimatable where Self: UIViewController
to narrow it down so that I can use self
when adding observers.
Now I can create a view controller that adopts this protocol and e.g. changes constraint when keyboard shows up:
import UIKit
class ViewController: UIViewController, KeyboardAnimatable {
@IBOutlet weak var constraintYCenter: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
setupKeyboardNotifcationListeners()
}
func keyboardWillShowAnimation() -> (() -> Void)? {
return {
self.constraintYCenter.constant = 100
self.view.layoutIfNeeded()
}
}
func keyboardWillHideAnimation() -> (() -> Void)? {
return nil
}
// func keyboardWillShow(notification: NSNotification) {
// print("will show")
// }
//
// func keyboardWillHide(notification: NSNotification) {
// print("will hide")
// }
deinit {
removeKeyboardNotificationListeners()
}
}
But when I run this code the app crashes with following error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[KeyboardAnimatableDemo.ViewController keyboardWillShow:]:
unrecognized selector sent to instance 0x7fb429f192d0'
It looks like my view controller doesn't use keyboardWillShow:
method implemented in protocol extension.
When I uncomment these two methods implemented directly in ViewController
they work (logs are printed) but then whole concept with common keyboard animator is useless.
I thought there might be something weird happening with self
in setupKeyboardNotifcationListeners()
but even if I change it to e.g. func setupKeyboardNotifcationListeners(vc: UIViewController)
and use vc
instead of self
when registering observer nothing changes.
One possible solution could be also creating UIViewController extension only for a subset of controllers that adopt this protocol e.g. something like this extension UIViewcontroller where Self: KeyboardAnimatable
but this one doesn't compile and I haven't found any example of such definition so it may even not be possible in Swift.
Did anyone try to achieve something similar and he succeeded?
Upvotes: 4
Views: 985
Reputation: 365
I solved this problem by removing protocol and introducing "empty" implementation for those methods. Now Extension
for keyboard extends directly UIViewController so it should be quite easy to use.
Code is on gist: https://gist.github.com/AdamSliwakowski/6fa1e920254ce584d203
Upvotes: 1