Xcode
Swift
RxSwift
RxCocoa

RxSwiftのExamplesにしれっと入ってる双方向データバインディングの演算子がイケてた

More than 1 year has passed since last update.

こんばんは!:octocat:

RxSwift (RxCocoa)を使ってモデル←→ビューのお互いの更新を伝え合う双方向データバインディングをやってみます。

SwiftBond では双方向データバインディングがサポートされているのですが RxSwiftのドキュメントにはそのような記述はなく、綺麗に書く方法ないかな〜とExamplesを眺めていたらそれっぽいものを見つけたのでご紹介します。

やってること

hoge.gif

  • 登場人物
    • ラベル
      • 最新の値を表示し続ける
    • テキストフィールド ←こいつに双方向bindを実装します
      • 最新の値を表示し続ける
      • 値を入力できる
    • ボタン
      • 値を初期値に変更できる

このUIにおいて、

  • テキストフィールドで入力した内容が即時ラベルに反映される。
  • ボタンの押下で書き換わった内容が即時テキストフィールドに反映される。

という点において双方向のデータバインディングを実現してみます。

Before

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    private var item = Variable<String?>("Hello!")
    private let disposeBag = DisposeBag()

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!

    @IBAction func resetButtonHandler(_ sender: UIButton) {
        item.value = "Hello!"
    }

    @IBAction func textFieldHandler(_ sender: UITextField) {
        item.value = sender.text
    }

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

    private func setup() {
        // モデルからビューへ
        item.asObservable().bind(to: textField.rx.text).disposed(by: disposeBag)
        // ビューからモデルへ
        textField.rx.text.subscribe(onNext: { [weak self] text in
            self?.item.value = text
        }).disposed(by: disposeBag)

        // 最新の値をラベルで表示しておく
        item.asObservable().bind(to: label.rx.text).disposed(by: disposeBag)
    }
}

ここで Bidirectional Operator <-> の登場です。

左辺にビュー、右辺にモデルが来るように書きます。
さらにそのままだと返り値が未使用と言われるので _ = を先頭につけておきます。
(一応例にもそう書いてあったのですが、万が一メモリリークの危険性踏んでたら教えて頂きたく...! :bow:

After

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    private var item = Variable<String?>("Hello!")
    private let disposeBag = DisposeBag()

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!

    @IBAction func resetButtonHandler(_ sender: UIButton) {
        item.value = "Hello!"
    }

    @IBAction func textFieldHandler(_ sender: UITextField) {
        item.value = sender.text
    }

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

    private func setup() {
        _ = textField.rx.text <-> item // ここ(´∀` )
        item.asObservable().bind(to: label.rx.text).disposed(by: disposeBag)
    }
}

ユーザID+パスワードや、住所氏名年齢電話番号・・・のような設定するコンポーネントが多い画面で活躍してくれそうです!
だんだんRxSwiftが面白くなってきた今日このごろです:relieved: おやすみなさい。