Skip to content

Instantly share code, notes, and snippets.

@roymckenzie
Last active September 6, 2022 19:42
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save roymckenzie/35684bf3ae6df02f60a41490a9843008 to your computer and use it in GitHub Desktop.
Save roymckenzie/35684bf3ae6df02f60a41490a9843008 to your computer and use it in GitHub Desktop.
Easy way to get your view controllers to respect appearance of the keyboard.
// KeyboardAvoidable
// Roy McKenzie
protocol KeyboardAvoidable: class {
func addKeyboardObservers(customBlock: ((CGFloat) -> Void)?)
func removeKeyboardObservers()
var layoutConstraintsToAdjust: [NSLayoutConstraint] { get }
}
var KeyboardShowObserverObjectKey: UInt8 = 1
var KeyboardHideObserverObjectKey: UInt8 = 2
extension KeyboardAvoidable where Self: UIViewController {
var keyboardShowObserverObject: NSObjectProtocol? {
get {
return objc_getAssociatedObject(self,
&KeyboardShowObserverObjectKey) as? NSObjectProtocol
}
set {
objc_setAssociatedObject(self,
&KeyboardShowObserverObjectKey,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var keyboardHideObserverObject: NSObjectProtocol? {
get {
return objc_getAssociatedObject(self,
&KeyboardHideObserverObjectKey) as? NSObjectProtocol
}
set {
objc_setAssociatedObject(self,
&KeyboardHideObserverObjectKey,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func addKeyboardObservers(customBlock: ((CGFloat) -> Void)? = nil) {
keyboardShowObserverObject = NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow,
object: nil,
queue: nil) { [weak self] notification in
guard let height = self?.getKeyboardHeightFrom(notification: notification) else { return }
if let customBlock = customBlock {
customBlock(height)
return
}
self?.layoutConstraintsToAdjust.forEach {
$0.constant = height
}
UIView.animate(withDuration: 0.2){
self?.view.layoutIfNeeded()
}
}
keyboardHideObserverObject = NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide,
object: nil,
queue: nil) { [weak self] notification in
if let customBlock = customBlock {
customBlock(0)
return
}
self?.layoutConstraintsToAdjust.forEach {
$0.constant = 0
}
UIView.animate(withDuration: 0.2){
self?.view.layoutIfNeeded()
}
}
}
private func getKeyboardHeightFrom(notification: Notification) -> CGFloat {
guard let info = notification.userInfo else { return .leastNormalMagnitude }
guard let value = info[UIKeyboardFrameEndUserInfoKey] as? NSValue else { return .leastNormalMagnitude }
let keyboardSize = value.cgRectValue.size
return keyboardSize.height
}
func removeKeyboardObservers() {
if let keyboardShowObserverObject = keyboardShowObserverObject {
NotificationCenter.default.removeObserver(keyboardShowObserverObject)
}
if let keyboardHideObserverObject = keyboardHideObserverObject {
NotificationCenter.default.removeObserver(keyboardHideObserverObject)
}
keyboardShowObserverObject = nil
keyboardHideObserverObject = nil
}
}
// Example Implementation
// final class NiceViewController: UIViewController {
//
// @IBOutlet weak var scrollViewBottomConstraint: NSLayoutConstraint!
//
// override func viewWillAppear(animated: Bool) {
// super.viewWillAppear(animated)
//
// addKeyboardObservers()
// }
//
// override func viewWillDisappear(animated: Bool) {
// super.viewWillDisappear(animated)
//
// removeKeyboardObservers()
// }
// }
//
// extension NiceViewController: KeyboardAvoidable {
//
// var layoutConstraintsToAdjust: [NSLayoutConstraint] {
// return [scrollViewBottomConstraint]
// }
// }
@MoridinBG
Copy link

MoridinBG commented Aug 24, 2016

addObserverForName(name:object obj:queue:usingBlock block:) returns an opaque object, that represents the observer.

NSNotificationCenter.defaultCenter().removeObserver(self ....

Does not remove this observer, because it is not self! You have to store this object and call remove observer with it as the parameter.
https://developer.apple.com/reference/foundation/nsnotificationcenter/1411723-addobserverforname

@roymckenzie
Copy link
Author

Great point @MoridinBG. Working a bit with @Nadohs we came up with this solution to clean up the notifications per your comment:

https://gist.github.com/roymckenzie/05bb47a3d8526eeedeffe6e42a0c178c

@roymckenzie
Copy link
Author

roymckenzie commented Jan 6, 2017

Updated this to handle the opaque objects created by the addObserver methods and remove observers from them when calling removeKeyboardObservers. Also Swift 3 goodness. Also custom callback...

@Drenov
Copy link

Drenov commented Dec 1, 2017

Don't removeKeyboardObservers() should be called on the beginning of addKeyboardObservers() to prevent issues when it was called more than once?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment