前言
在iOS想要監(jiān)聽鍵盤的話是通過注冊通知、接收鍵盤發(fā)來的通知實(shí)現(xiàn)的,雖然步驟不是很多,不過由于鍵盤的狀態(tài)很多(有6種),如果需求要監(jiān)聽所有的狀態(tài),想想你要把相同的代碼寫6次,是有多煩人。而且適配的系統(tǒng)在iOS 9以下還要把一個(gè)個(gè)通知移除。這是有多不簡潔...不過自從我遇到了Typist, 感覺優(yōu)雅簡潔到要死。
Typist簡介
Typist is a small, drop-in Swift UIKit keyboard manager for iOS apps. It helps you manage keyboard's screen presence and behavior without notification center and Objective-C.
也就是說Typist主要方便了鍵盤顯示的事件,那么他究竟是怎么方便的呢?下面這段代碼就是Typist的簡單使用:
let keyboard = Typist.shared
keyboard
.on(event: .didShow) { (options) in
print("New Keyboard Frame is \(options.endFrame).")
}
.on(event: .didHide) { (options) in
print("It took \(options.animationDuration) seconds to animate keyboard out.")
}
.start()
Oh, my god.怎么看上去和Rx有點(diǎn)像呀。哈哈,確實(shí)是的,不過這里作者只是運(yùn)用了方法的鏈?zhǔn)秸{(diào)用。并沒有Rx那么高大上哈。
如何你想要移除鍵盤監(jiān)聽,那么直接調(diào)用keyboard.clear()即可。
不過這里有點(diǎn)需要注意下,當(dāng)有多個(gè)不同的對象都要監(jiān)聽鍵盤時(shí),不要使用該單例。也就說這種情況下就需要你自己去創(chuàng)建Typist對象了,不要使用Typist.shared這個(gè)會導(dǎo)致你其中某些對象接受不到通知。
從鍵盤監(jiān)聽說起
在不使用Typist的情況下,我們是怎么做鍵盤監(jiān)聽的呢?

具體步驟如圖所示。不過在如果你的應(yīng)用支持iOS 9+以上,那么就可以不用去移除通知,這個(gè)在Apple文檔里也有說明。
可能只是一張圖你看不出來有多煩人,那么我用代碼演示下是這樣的。

而且這僅僅只是監(jiān)聽了鍵盤的實(shí)現(xiàn)方法,就已經(jīng)需要這么多代碼了。那么我們要是遇到需求要監(jiān)聽所有的鍵盤通知呢?那么上面代碼就成了這樣:

一堆樣式相同的模板代碼,有人會說,我需要一個(gè)函數(shù)封裝下,不過還是還是不夠
Typist簡潔。
那么Typiest是如何做鍵盤監(jiān)聽的
let keyboard = Typist.shared // #1
keyboard
.on(event: .didShow) { (options) in // #2
print("New Keyboard Frame is \(options.endFrame).")
}
.on(event: .didHide) { (options) in // #3
print("It took \(options.animationDuration) seconds to animate keyboard out.")
}
.start() // #4
還是從簡介中的這段代碼說起,#1處是使用單例創(chuàng)建一個(gè)Typist對象;#2 處監(jiān)聽了鍵盤顯示結(jié)束,閉包回調(diào)里的options包含了一些鍵盤信息,也就是NotificationCenter里面的info信息;#3 處監(jiān)聽了鍵盤的隱藏結(jié)束,閉包回調(diào)同上;#4 在這里開始監(jiān)聽,相當(dāng)與我們使用的addobserver: 方法。
具體的邏輯如下圖所示:

那么它內(nèi)部究竟是怎么做的呢?
public func on(event: KeyboardEvent, do callback: TypistCallback?) -> Self 方法
這個(gè)方式的實(shí)現(xiàn)很簡單,僅僅是將callbac回調(diào)放入一個(gè)dict里面,dict的key是event,然后返回自己。
public func on(event: KeyboardEvent, do callback: TypistCallback?) -> Self {
callbacks[event] = callback
return self
}
KeyboardEvent
作者在這里定義了一個(gè)枚舉來封裝了各個(gè)不同的鍵盤監(jiān)聽,然后我們在on方法里直接用傳入枚舉值就可以。
public enum KeyboardEvent {
/// Event raised by UIKit's `.UIKeyboardWillShow`.
case willShow
/// Event raised by UIKit's `.UIKeyboardDidShow`.
case didShow
/// Event raised by UIKit's `.UIKeyboardWillShow`.
case willHide
/// Event raised by UIKit's `.UIKeyboardDidHide`.
case didHide
/// Event raised by UIKit's `.UIKeyboardWillChangeFrame`.
case willChangeFrame
/// Event raised by UIKit's `.UIKeyboardDidChangeFrame`.
case didChangeFrame
}
start 方法
start在這里僅僅是注冊了鍵盤通知,注意下addObserver方法的參數(shù)。這里的event就是上面的KeyboardEvent,通過event映射到NSNotification.Name 和 SEL。
/// Starts listening to events and calling corresponding events handlers.
public func start() {
let center = NotificationCenter.`default`
for event in callbacks.keys {
center.addObserver(self, selector: event.selector, name: event.notification, object: nil)
}
}
event.selector 與 event.notification 屬性
這兩個(gè)KeyboardEvent擴(kuò)展下的私有屬性分別返回上面提到的 NSNotification.Name 和 SEL
fileprivate extension Typist.KeyboardEvent {
var notification: NSNotification.Name {
get {
switch self {
case .willShow:
return .UIKeyboardWillShow
case .didShow:
return .UIKeyboardDidShow
case .willHide:
return .UIKeyboardWillHide
case .didHide:
return .UIKeyboardDidHide
case .willChangeFrame:
return .UIKeyboardWillChangeFrame
case .didChangeFrame:
return .UIKeyboardDidChangeFrame
}
}
}
var selector: Selector {
get {
switch self {
case .willShow:
return #selector(Typist.keyboardWillShow(note:))
case .didShow:
return #selector(Typist.keyboardDidShow(note:))
case .willHide:
return #selector(Typist.keyboardWillHide(note:))
case .didHide:
return #selector(Typist.keyboardDidHide(note:))
case .willChangeFrame:
return #selector(Typist.keyboardWillChangeFrame(note:))
case .didChangeFrame:
return #selector(Typist.keyboardDidChangeFrame(note:))
}
}
}
}
SEL 在哪里實(shí)現(xiàn)
關(guān)于上面的event.selector是如何實(shí)現(xiàn)調(diào)用的。實(shí)際上是通過閉包的形式調(diào)用,作者在這里實(shí)現(xiàn)了每一個(gè)的監(jiān)聽響應(yīng)方法,然后在該方法里去調(diào)用閉包來做。具體閉包的實(shí)現(xiàn)是使用者來實(shí)現(xiàn)的,然后通過key為event的dict來獲取閉包。
舉例代碼:
internal func keyboardWillShow(note: Notification) {
if let callback = callbacks[.willShow] {
callback(keyboardOptions(fromNotificationDictionary: note.userInfo))
}
}
KeyboardOptions
這里主要是一些與鍵盤有關(guān)的信息。

總結(jié)

簡單總結(jié)下該優(yōu)雅的邏輯:
let keyboard = Typist.shared- 調(diào)用
func on(event: KeyboardEvent, do callback: TypistCallback?),- 該方法中僅僅做了一件事:
callbacks[event] = callback - 顯然這個(gè)閉包是我們?nèi)?shí)現(xiàn)的。
- 該方法中僅僅做了一件事:
- 調(diào)用
start()方法- 遍歷了
callbacks, 依次實(shí)現(xiàn)了addObserve方法,主要兩個(gè)參數(shù)是selector: event.selector, name: event.notification - 其中
event.notificationevent 和keyboard NSNotification.Name 的映射,event.selector中去調(diào)用了callbacks中的閉包,也就是我們的實(shí)現(xiàn)事件。
- 遍歷了
- 可以調(diào)用stop()方法移除通知。
- 本質(zhì)上是調(diào)用了
removeObserver方法.
- 本質(zhì)上是調(diào)用了
參考
Typist GitHub Repo
原文地址:https://vsccw.com/2017/02/26/for-typist/