【iOS】RxSwiftを使ってMVVMなカウントダウンタイマーの作り方


リアクティブなカウントダウンタイマーの作り方

手書きでQiita書いたので誤字脱字あったらお許しください...

ViewController

ViewController.swift

class ViewController: UIViewController {

  @IBOutlet private weak var countLabel: UILabel!
  @IBOutlet private weak var startButton: UIButton!
  @IBOutlet private weak var stopButton: UIButton!

  private var viewModel: ViewModel!
  private let disposeBag = DisposeBag()

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

  private func setupViewModel() {
    let input = ViewModel.Input(
      startButtonTapped: startButton.rx.tap.asObservable(),
      stopButtonTapped: stopButton.rx.tap.asObservable()
    )
    viewModel = ViewModel(trigger: input)
    // output
    viewModel.output().count
      .map { "あと:\($0)秒" }
      .bind(to: countLabel.rx.text)
      .disposed(by: disposeBag)
  }
}

ViewModel

ViewModel.swift

class ViewModel {

  struct Input {
    var startButtonTapped: Observable<Void>
    var stopButtonTapped: Observable<Void>
  }

  struct Output {
    var count: Observable<Int>
  }

  private var input: ViewModel.Input!
  private var output: ViewModel.Output!

  private let isValidTimerRelay = BehaviorRelay<Bool>(value: false)
  private let countRelay = BehaviorRelay<Int>(value: 60) // 初期値: 60秒 (任意)

  private let disposeBag = DisposeBag()

  init(trigger: ViewModel.Input) {
    input = trigger
    output = ViewModel.Output(
      count: countRelay.asObservable()
    )

    bind()
    setupTimer()
  }


  // MARK: Actions
  private func bind() {
    // start button tapped
    input.startButtonTapped
      .map { true }
      .bind(to: isValidTimerRelay)
      .disposed(by: disposeBag)

    // stop button tapped
    input.stopButtonTapped
      .map { false }
      .bind(to: isValidTimerRelay)
      .disposed(by: disposeBag)
  }

  // MARK: Timer
  private func setupTimer() {
    let period = DispatchTimeInterval.seconds(1) // 1秒間隔で実行
    isValidTimerRelay
      .flatMapLatest {
        $0 ? Observable<Int>.interval(period, scheduler: MainScheduler.instance) : Observable.empty()
      }
      .map { [weak self] _ in
        guard let value = self?.countRelay.value else { return nil }
        return value - 1
      }
      .filter { [weak self] _ in
        guard let value = self?.timerValueRelay.value else { return false }
        return value >= 0 ? true : false 
      }
      .unwrap()
      .bind(to: countRelay)
      .disposed(by: disposeBag)
  }

  // MARK: -- OUTPUT
  func output() -> ViewModel.Output {
    output
  }

}

こんな感じで動くかと!! ぜひ試してみてください😁

参考

RxSwift | GitHub
[iOS] RxSwiftのIntervalオペレータを使用して一定間隔で処理を行う
【iOS】MVVMについて