1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

RxSwiftのObservableでON/OFFの状態遷移を表現

Posted at

環境

RxSwiftとRxCocoaはCocoaPodsなどでインストールしましょう。

  • XCode (12.2)
  • Swift (5.3.1)
  • RxSwift (6.0.0)
  • RxCocoa (6.0.0)

実装(ON/OFF)

上層で流れてくる値に関わらずストリームに流れる値がfalse->true->falseと切り替わる関数です。
Rxオペレーターのscan()を使って前回の状態を参照しているところがポイントです。
scan()は購読するだけでは初期値を流さないのでstartWith()で初期値を流しています。

extension Observable {
    func flipflop(initialValue: Bool) -> Observable<Bool>{
        scan(initialValue) { current, _ in !current }
            .startWith(initialValue)
    }
}

適当なViewControllerで動くサンプルです。ボタンをタップするとON/OFFが切り替わっています。

import UIKit
import RxSwift
import RxCocoa

final class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // ラベル配置
        let label = UILabel()
        view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
        // ボタン配置
        let button = UIButton(type: .system)
        button.setTitle("Switch", for: .normal)
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 10)
        ])

        // イベント接続
        _ = button.rx.tap
            .asObservable()
            .flipflop(initialValue: false)
            .map { $0 ? "ON" : "OFF" }
            .bind(to: label.rx.text)
    }
}

実装(状態が3つ以上)

先ほどはON/OFFしか状態がない場合についての実装になりましたが、今度は3種類以上の状態を持つ場合について一般化してみましょう。
transition引数で次の状態を与えることで状態の遷移を表現しています。
先程実装したflipflop()state(initialValue: transition:)の応用で置き換えられます。

extension Observable {
    func state<T>(initialValue: T,
                  transition: @escaping (T) -> T) -> Observable<T>{
        scan(initialValue) { current, _ in transition(current) }
            .startWith(initialValue)
    }

    // 上記の関数を使うとこのような実装になる
    func flipflop(initialValue: Bool) -> Observable<Bool>{
        state(initialValue: false,
              transition: !)
    }
}

こちらはアプリ上で動くサンプルです。enum Handはじゃんけんの手を表現していて、ボタンを押すと表示されている手に勝つようグー->パー->チョキの順で表示が変わります。


// じゃんけんの手
enum Hand {
    case rock
    case paper
    case scissors

    var name: String {
        switch self {
        case .rock: return "グー"
        case .paper: return "パー"
        case .scissors: return "チョキ"
        }
    }

    // グー<-パー<-チョキ<-グーの順に勝つ
    var defeatedBy: Hand {
        switch self {
        case .rock: return .paper
        case .paper: return .scissors
        case .scissors: return .rock
        }
    }
}


final class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // UIは同じなので省略

        // イベント接続
        _ = button.rx.tap
            .asObservable()
            .state(initialValue: Hand.rock,
                   transition: \.defeatedBy)
            .map(\.name)
            .bind(to: label.rx.text)
    }
}
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?