iOSのキーボード周りの扱いを簡単にするKeyboardObserverを作りました

[Swiftでライブラリを公開する](http://qiita.com/morizotter/items/2e45f8f94c1010ebd69f)という記事を書いた際に、手元のコードを一つマイクロライブラリ化してみました。それが、 [KeyboardObserver](https://github.com/morizotter/KeyboardObserver) です。

![スクリーンショット 2015-12-16 04.50.38.png](https://qiita-image-store.s3.amazonaws.com/0/24041/05acefcf-f65c-5a61-a8b4-b7f33f60b2a3.png)

## 特徴

iOS開発で面倒なキーボードのイベントの処理を簡潔にします。キーボードのイベントはまず **UINotificationの通知を監視** して、 **渡ってくる辞書から値を取得してキャストして** やっと高さ計算などをしてビューのサイズやinsetを変えると思います。やりたいことは、ビューの高さを変えることだけなのですが、そのための準備が面倒です。それを簡単に扱えるようにしたのが [KeyboardObserver](https://github.com/morizotter/KeyboardObserver) です。

通知をライブラリ内部で取得して、イベントを取り出し、専用のstructにマップして、クロージャで扱えるようにしています。コードの比較をしたほうがわかりやすいと思いますので書いてみます。

まずは、通常のやり方から（ざっと眺めてみるだけで良いと思います）。ここではinsetsを変えていますが、Viewのサイズを変えることのほうが多いとは思います。

## 通常のやり方


```Swift
let keyboardNotifications = [
    UIKeyboardWillShowNotification,
    UIKeyboardWillHideNotification,
    UIKeyboardWillChangeFrameNotification
]

override func viewDidLoad() {
    super.viewDidLoad()
}

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    keyboardNotifications.forEach {
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardEventNotified:", name: $0, object: nil)
    }
}

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)

    keyboardNotifications.forEach {
        NSNotificationCenter.defaultCenter().removeObserver(self, name: $0, object: nil)
    }
}

func keyboardEventNotified(notification: NSNotification) {
    guard let userInfo = notification.userInfo else { return }
    let keyboardFrameEnd = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
    let curve = UIViewAnimationOptions(rawValue: UInt(userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber))
    let duration = NSTimeInterval(userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber)
    let distance = UIScreen.mainScreen().bounds.height - keyboardFrameEnd.origin.y
    let bottom = distance >= bottomLayoutGuide.length ? distance : bottomLayoutGuide.length

    UIView.animateWithDuration(duration, delay: 0.0, options: [curve], animations:
        { [weak self] () -> Void in
            self?.textView.contentInset.bottom = bottom
            self?.textView.scrollIndicatorInsets.bottom = bottom
        } , completion: nil)
}

```

ううう、面倒臭い。Swiftの記法で少し短く書けるようにはなっていますが、面倒です。通知を監視して、高さ変更のための関数を作って、通知を消して...。

## KeyboardObserverの場合

```
let keyboard = KeyboardObserver()

override func viewDidLoad() {
    super.viewDidLoad()

    keyboard.observe { [weak self] (event) -> Void in
        guard let s = self else { return }
        switch event.type {
        case .WillShow, .WillHide, .WillChangeFrame:
            let distance = UIScreen.mainScreen().bounds.height - event.keyboardFrameEnd.origin.y
            let bottom = distance >= s.bottomLayoutGuide.length ? distance : s.bottomLayoutGuide.length

            UIView.animateWithDuration(event.duration, delay: 0.0, options: [event.curve], animations:
                { [weak self] () -> Void in
                    self?.textView.contentInset.bottom = bottom
                    self?.textView.scrollIndicatorInsets.bottom = bottom
                } , completion: nil)
        default:
            break
        }
    }
}
```

`keyboard.observe` の中で完結します。イベントの種類は`event.type`にenumとして入っているので、そこで処理を分けることも簡単です。KeyboardObserverの `deinit` とともに通知の監視は終了するので、監視の開始と終了を書く必要もありません。意図的に通知を受けないようにしたい場合は、 `enabled` をfalseにすればOKです。

## KeyboardEvent

closureで渡ってくるeventは下記のプロパティを持っています。UINotificationのuserInfoの中身がそのままキャストされて用意されています。

```
public struct KeyboardEvent {
    public let type: KeyboardEventType
    public let keyboardFrameBegin: CGRect
    public let keyboardFrameEnd: CGRect
    public let curve: UIViewAnimationOptions
    public let duration: NSTimeInterval
    public var isLocal: Bool?
    ...
}
```

## KeyboardEventType

KeyboardEventのプロパティの一つであるKeyboardEventTypeは下記のようになっています。

```
public enum KeyboardEventType {
    case WillShow
    case DidShow
    case WillHide
    case DidHide
    case WillChangeFrame
    case DidChangeFrame
    ...
}
```

これに加えて、`public var notificationName: String` もあります。これは、通知に関係するイベントを全てenumで網羅しています。

[Carthage](https://github.com/Carthage/Carthage)と[Cocoapods](https://t.co/DG6w5eTHpt)で公開しています。是非、ご利用ください〜。

## KeyboardObserver

- [morizotter/KeyboardObserver](https://github.com/morizotter/KeyboardObserver)