Created
July 26, 2021 04:42
-
-
Save bergusman/cab78cd3d194c3612bc1f22ce899b6c7 to your computer and use it in GitHub Desktop.
Keyboard Observer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// KeyboardObserver.swift | |
// Vitaly Berg | |
// | |
// Created by Vitaly Berg on 7/21/21. | |
// Copyright © 2021 Vitaly Berg. All rights reserved. | |
// | |
import UIKit | |
extension Notification.Name { | |
static let keyboardStateChanged = Notification.Name("KeyboardStateChanged") | |
} | |
class KeyboardObserver { | |
class var keyboardHeight: CGFloat { | |
let h = UIScreen.main.bounds.height | |
if h <= 667 { | |
return 216 | |
} else if h <= 736 { | |
return 226 | |
} else if h <= 844 { | |
return 291 | |
} else { | |
return 301 | |
} | |
} | |
class var suggestionsHeight: CGFloat { | |
let h = UIScreen.main.bounds.height | |
if h <= 568 { | |
return 38 | |
} else if h <= 667 { | |
return 44 | |
} else { | |
return 45 | |
} | |
} | |
class var keyboardAndSuggestionsHeight: CGFloat { | |
return keyboardHeight + suggestionsHeight | |
} | |
var isEnabled = true | |
init(enabled: Bool = true) { | |
isEnabled = enabled | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: UIResponder.keyboardDidShowNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) | |
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidChangeFrame), name: UIResponder.keyboardDidChangeFrameNotification, object: nil) | |
} | |
deinit { | |
NotificationCenter.default.removeObserver(self) | |
} | |
// MARK: - State | |
enum State { | |
case willShow(UserInfo) | |
case didShow(UserInfo) | |
case willHide(UserInfo) | |
case didHide(UserInfo) | |
} | |
private(set) var state: State? | |
private(set) var frame: CGRect? | |
// MARK: - Handlers | |
struct UserInfo { | |
let frameBegin: CGRect | |
let frameEnd: CGRect | |
let animationDuration: TimeInterval | |
let animationOptions: UIView.AnimationOptions | |
} | |
typealias Handler = (UserInfo) -> Void | |
var willShow: Handler? | |
var didShow: Handler? | |
var willHide: Handler? | |
var didHide: Handler? | |
var willChangeFrame: Handler? | |
var didChangeFrame: Handler? | |
// MARK: - Notifications | |
@objc private func keyboardWillShow(notification: Notification) { | |
guard isEnabled else { | |
return | |
} | |
guard let userInfo = UserInfo(notification.userInfo) else { | |
return | |
} | |
state = .willShow(userInfo) | |
willShow?(userInfo) | |
} | |
@objc private func keyboardDidShow(notification: Notification) { | |
guard isEnabled else { | |
return | |
} | |
guard let userInfo = UserInfo(notification.userInfo) else { | |
return | |
} | |
state = .didShow(userInfo) | |
didShow?(userInfo) | |
} | |
@objc private func keyboardWillHide(notification: Notification) { | |
guard isEnabled else { | |
return | |
} | |
guard let userInfo = UserInfo(notification.userInfo) else { | |
return | |
} | |
state = .willHide(userInfo) | |
willHide?(userInfo) | |
} | |
@objc private func keyboardDidHide(notification: Notification) { | |
guard isEnabled else { | |
return | |
} | |
guard let userInfo = UserInfo(notification.userInfo) else { | |
return | |
} | |
state = .didHide(userInfo) | |
didHide?(userInfo) | |
} | |
@objc private func keyboardWillChangeFrame(notification: Notification) { | |
guard isEnabled else { | |
return | |
} | |
guard let userInfo = UserInfo(notification.userInfo) else { | |
return | |
} | |
frame = userInfo.frameEnd | |
willChangeFrame?(userInfo) | |
} | |
@objc private func keyboardDidChangeFrame(notification: Notification) { | |
guard isEnabled else { | |
return | |
} | |
guard let userInfo = UserInfo(notification.userInfo) else { | |
return | |
} | |
frame = userInfo.frameEnd | |
didChangeFrame?(userInfo) | |
} | |
} | |
extension KeyboardObserver.State { | |
var isShowing: Bool { | |
if case .willShow = self { | |
return true | |
} | |
return false | |
} | |
var isShown: Bool { | |
if case .didShow = self { | |
return true | |
} | |
return false | |
} | |
var isHidding: Bool { | |
if case .willHide = self { | |
return true | |
} | |
return false | |
} | |
var isHidden: Bool { | |
if case .didHide = self { | |
return true | |
} | |
return false | |
} | |
} | |
extension KeyboardObserver.UserInfo { | |
init?(_ info: [AnyHashable : Any]?) { | |
guard let info = info else { | |
return nil | |
} | |
guard let frameBegin = (info[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue else { | |
return nil | |
} | |
guard let frameEnd = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { | |
return nil | |
} | |
guard let animationDuration = info[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval else { | |
return nil | |
} | |
guard let animationCurve = info[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else { | |
return nil | |
} | |
self.init( | |
frameBegin: frameBegin, | |
frameEnd: frameEnd, | |
animationDuration: animationDuration, | |
animationOptions: UIView.AnimationOptions(rawValue: animationCurve << 16) | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class HelloViewController: UIViewController { | |
@IBOutlet weak var bottomConstraint: NSLayoutConstraint! | |
private let keyboardObserver = KeyboardObserver() | |
private func setup() { | |
bottomConstraint.constant = KeyboardObserver.keyboardAndSuggestionsHeight + 24 | |
keyboardObserver.willShow = { [weak self] (userInfo) in | |
guard let self = self else { return } | |
self.bottomConstraint.constant = userInfo.frameEnd.height - self.view.safeAreaInsets.bottom + 24 | |
} | |
keyboardObserver.willHide = { [weak self] (userInfo) in | |
guard let self = self else { return } | |
} | |
} | |
// MARK: - UIViewController | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
setup() | |
} | |
override func viewDidAppear(_ animated: Bool) { | |
super.viewDidAppear(animated) | |
keyboardObserver.isEnabled = true | |
} | |
override func viewWillDisappear(_ animated: Bool) { | |
super.viewWillDisappear(true) | |
keyboardObserver.isEnabled = false | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment