リアクティブなカウントダウンタイマーの作り方
手書きで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 isTimerActionSubject = PublishSubject<Bool>()
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: isTimerActionSubject)
.disposed(by: disposeBag)
// stop button tapped
input.stopButtonTapped
.map { false }
.bind(to: isTimerActionSubject)
.disposed(by: disposeBag)
}
// MARK: Timer
private func setupTimer() {
isTimerActionSubject.asObservable()
.flatMap { $0 ? Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance) : .empty() }
.withLatestFrom(countRelay) { $1 - $0 }
.bind(to: countRelay)
.disposed(by: disposeBag)
}
// MARK: -- OUTPUT
func output() -> ViewModel.Output {
return output
}
}
こんな感じで動くかと!!
ぜひ試してみてください😁
参考
RxSwift | GitHub
[iOS] RxSwiftのIntervalオペレータを使用して一定間隔で処理を行う
【iOS】MVVMについて