iOS KeyboardObserving(beta)

// TODO: 开篇

  • 常见问题(本文要解决的)
  • 思路(用protocol个extension)

1、在Notification的extension中方便快捷地获取键盘的动画时长和尺寸

extension Notification {

    public var keyboardSize: CGSize? {
        return (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size
    }

    public var keyboardAnimationDuration: TimeInterval? {
        return userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval
    }

}

2、定义协议,协议中需要暴露两方面的接口,一是开启和结束键盘状态的监听,二是监听到键盘升降之后的action

public protocol KeyboardObserving: AnyObject {
    func startObservingKeyboard()
    func stopObservingKeyboard()

    func keyboardWillShow(notification: Notification)
    func keyboardWillHide(notification: Notification)
}

UIViewController有生命周期,需要在特定的阶段开启或取消对通知的监听,在willAppearwillDisappear阶段调用func startObservingKeyboard()func stopObservingKeyboard()是比较方便的。尤其是present出另一个viewController的情况下,如果没有取消对键盘的监听,那么第二个ViewController触发键盘可能会影响第一个控制器执行升起键盘后的动画。

这个协议希望被需要监听键盘即将升降的对象来遵守,通常是UIViewController,所以我们在默认实现中也通过where Self: UIViewController限制为UIViewController

public extension KeyboardObserving where Self: UIViewController {

    func startObservingKeyboard() {
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: nil) { [weak self] (notification) in
            self?.keyboardWillShow(notification: notification)
        }
        NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: nil) { [weak self] (notification) in
            self?.keyboardWillHide(notification: notification)
        }
    }

    func stopObservingKeyboard() {
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil)
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil)
    }

}

3、用例

class MediaBrowseViewController: UIViewController, KeyboardObserving {
   override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // 在生命周期的即将显示阶段 调用协议中的开启监听方法的默认实现
        self.startObservingKeyboard()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // 在生命周期的即将消失阶段 调用协议中的结束监听方法的默认实现
        self.stopObservingKeyboard()
    }

    // MARK: - KeyboardObserving
    func keyboardWillShow(notification: Notification) {
        self.replyInputView.showMaskIfPossible()
        if let height = notification.keyboardSize?.height, let duration = notification.keyboardAnimationDuration {
            // 这里执行键盘即将升起时UI变化的任务
            UIView.animate(withDuration: duration) {
                self.replyInputView.willAscend()
                self.replyInputView.snp.updateConstraints { make in
                    make.bottom.equalToSuperview().offset(-height)
                }
                self.view.layoutSubviews()
            }
        }
    }

    func keyboardWillHide(notification: Notification) {
        if let duration = notification.keyboardAnimationDuration {
            // 这里执行键盘即将降下时UI变化的任务
            UIView.animate(withDuration: duration) {
                self.replyInputView.willDescend()
                self.replyInputView.snp.updateConstraints { make in
                    make.bottom.equalToSuperview().offset(-tabbarHeight)
                }
                self.view.layoutIfNeeded()
            }
        }
    }
}

4、关键词:

  • UIViewController的生命周期
  • protocol
  • extension