RxSwiftでキーボードの高さを取得する

2021.11.26

RxSwiftでキーボードの高さを監視して処理したいということがあったので、気温も寒くなり色々と億劫になってしまいそうなところを気分は常夏で記事を書いていきたいと思います。

RxSwift

Rxは、Observableストリームから値やその他のイベントをブロードキャストおよびサブスクライブできるもの。具体的には、UIイベント受け取ったり、Web API レスポンスを受け取ったり、プロパティの変化を監視することが可能です。

RxSwiftは数あるReactiveXファミリーのひとつで、Swift用に実装されたRxライブラリになります。

この記事ではRxSwiftの詳細な説明やインストール方法などについては行いませんのでご了承下さい。RxSwiftの公式ドキュメントを見ていただければと思います。

開発環境

  • Xcode 13
  • Swift 5.5
  • RxSwift 6.2.0
  • RxCocoa 6.2.0

デモ

キーボードの高さを取得して、キーボードでサーフィンするようなものを作ってみました。

キーボード閉 キーボード開

RxSiwftでキーボードの高さを取得する

まずはNotificationCenterでキーボードの高さを取得するメソッドを作成しました。

import UIKit
import RxSwift

extension NotificationCenter {

    func keyboardHeight() -> Observable<CGFloat> {
        return Observable
            .from([
                self.rx.notification(UIResponder.keyboardWillShowNotification)
                    .map { notification -> CGFloat in
                        (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height ?? 0
                    },
                self.rx.notification(UIResponder.keyboardWillHideNotification)
                    .map { _ -> CGFloat in
                        0
                    }
            ])
            .merge()
    }
}

from

Summary:

Arrayを観測可能なシーケンスに変換する。

サマリーにもある通り、配列の要素を観測可能なものに変換しています。

UIResponder.keyboardFrameEndUserInfoKey

スクローン上でキーボードがアニメーションした後の長方形を確認するCGRectを含むNSValueで、その長方形フレームは現在のデバイスの向きの影響を受けます。

つまりはキーボードが出現した時のキーボードのCGRectを持っているNSValueここからキーボードの高さを取得します。

// NSValueの持っているCGRectから高さを取得
notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height

merge

merge() は複数の非同期処理を並列に実行することができます。つまり、Observable の渡される順番などが関係なく処理が早く終わった順にストリームに流れます。

下記画像はReactiveX - Mergeから引用させていただきました。

上部の2本のストリームがmergeされることで下部の1本のストリームになっており、処理が早く終わった順番に流れてきているのが分かると思います。

キーボードの高さを監視して処理を行う

今回でいうと、

fromで観測可能なシーケンスとなったキーボードが表示された時の高さ非表示になった時の高さがマージされ順番にストリームに流れてきます。

その流れてきた値を使って、何らかの処理を行うことができます。

NotificationCenter.default.keyboardHeight()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: {[weak self] keyboardHeight in
                // キーボードの高さを使って何かをする
            })
             .disposed(by: disposeBag)

キーボードでサーフィンをする

import UIKit
import RxSwift

class ViewController: UIViewController {

    @IBOutlet weak private var surferBotomConstraint: NSLayoutConstraint!

    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.keyboardHeight()
            .observe(on: MainScheduler.instance)
            .subscribe(onNext: { [weak self] keyboardHeight in
                if keyboardHeight == 0 {
                    self?.surferBotomConstraint.constant = 0
                } else {
                    self?.surferBotomConstraint.constant += keyboardHeight
                }
            })
            .disposed(by: disposeBag)
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        view.endEditing(true)
    }
}

今回はKeyboardHeightが新しい値に変わった時に、サーファーのBottomConstraintにキーボードの高さ分加えてあげて、波乗りならぬキーボード乗りを表現しています。

本来ならば、アニメーションも加えてスムースなキーボード乗りを表現した方がより良いですが今回は割愛させていただきます。

RxSwiftを用いたものではないですが、僕の過去の記事でキーボードの高さ×アニメーション使った表現の記事は書いているので良ければ見てあげてください。

おわりに

「そこの君、波じゃなくてキーボードに乗らないかい?」

クラスメソッドでは働く仲間を募集しています。

クラスメソッド採用サイト

参考