Mais conteúdo relacionado
Semelhante a RxSwiftをバインディングツールとして使ってみる (20)
RxSwiftをバインディングツールとして使ってみる
- 2. 自己紹介
• 一宮 浩教
• 徳島市内で働く
iOS/Android/Windows(ストアアプリ)
エンジニア
• https://twitter.com/hironytic
• https://github.com/hironytic
- 5. RxSwiftっぽく
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
let numbers = Observable.combineLatest(number1Field.rx_text,
number2Field.rx_text) { ($0, $1) }
numbers
.map { (number1, number2) in
return !number1.isEmpty && !number2.isEmpty
}
.bindTo(calcButton.rx_enabled)
.addDisposableTo(disposeBag)
calcButton.rx_tap
.withLatestFrom(numbers)
.map { (number1, number2) in
let n1 = Int(number1) ?? 0
let n2 = Int(number2) ?? 0
return String(n1 + n2)
}
.startWith("")
.bindTo(answerLabel.rx_text)
.addDisposableTo(disposeBag)
}
!今回の話では
忘れてください
ViewController
- 6. RxSwiftを使わないなら
override func viewDidLoad() {
super.viewDidLoad()
number1Field.text = ""
number2Field.text = ""
answerLabel.text = ""
calcButton.enabled = false
}
@IBAction private func calc(sender: AnyObject) {
let n1 = Int(number1Field.text ?? "") ?? 0
let n2 = Int(number2Field.text ?? "") ?? 0
answerLabel.text = String(n1 + n2)
}
@IBAction func number1Changed(sender: AnyObject) {
updateCalcState()
}
@IBAction func number2Changed(sender: AnyObject) {
updateCalcState()
}
private func updateCalcState() {
calcButton.enabled = !(number1Field.text?.isEmpty ?? true)
&& !(number2Field.text?.isEmpty ?? true)
}
ViewController
計算ボタンの
タップ
数値
入力時
- 7. 今回のやり方
var disposeBag = DisposeBag()
let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.number1Text
.bindTo(number1Field.rx_text)
.addDisposableTo(disposeBag)
viewModel.number2Text
.bindTo(number2Field.rx_text)
.addDisposableTo(disposeBag)
viewModel.calcEnabled
.bindTo(calcButton.rx_enabled)
.addDisposableTo(disposeBag)
viewModel.answerText
.bindTo(answerLabel.rx_text)
.addDisposableTo(disposeBag)
number1Field.rx_text
.bindTo(viewModel.number1ChangedAction)
.addDisposableTo(disposeBag)
number2Field.rx_text
.bindTo(viewModel.number2ChangedAction)
.addDisposableTo(disposeBag)
calcButton.rx_tap
.bindTo(viewModel.calcAction)
.addDisposableTo(disposeBag)
}
ViewControllerViewModel
に分割
ViewModelの
出力を
バインド
ViewModelの
入力へ
バインド
- 8. 今回のやり方
let number1Text: Observable<String>
let number2Text: Observable<String>
let calcEnabled: Observable<Bool>
let answerText: Observable<String>
let number1ChangedAction: AnyObserver<String>
let number2ChangedAction: AnyObserver<String>
let calcAction: AnyObserver<Void>
private let _number1Text = Variable<String>("")
private let _number2Text = Variable<String>("")
private let _calcEnabled = Variable<Bool>(false)
private let _answerText = Variable<String>("")
private let _calcAction = ActionObserver<Void>()
private let _number1ChangedAction = ActionObserver<String>()
private let _number2ChangedAction = ActionObserver<String>()
init() {
number1Text = _number1Text.asObservable()
number2Text = _number2Text.asObservable()
answerText = _answerText.asObservable()
calcEnabled = _calcEnabled.asObservable()
number1ChangedAction = _number1ChangedAction.asObserver()
number2ChangedAction = _number2ChangedAction.asObserver()
calcAction = _calcAction.asObserver()
_calcAction.handler = { [weak self] in self?.calc() }
_number1ChangedAction.handler = { [weak self] in self?.number1Changed($0) }
_number2ChangedAction.handler = { [weak self] in self?.number2Changed($0) }
}
ViewModel
外に公開した
入出力
状態の保持
- 9. 今回のやり方
private func calc() {
let n1 = Int(_number1Text.value) ?? 0
let n2 = Int(_number2Text.value) ?? 0
_answerText.value = String(n1 + n2)
}
private func number1Changed(value: String) {
_number1Text.value = value
updateCalcState()
}
private func number2Changed(value: String) {
_number2Text.value = value
updateCalcState()
}
private func updateCalcState() {
_calcEnabled.value = !_number1Text.value.isEmpty
&& !_number2Text.value.isEmpty
}
ViewModel
- 13. ViewModelのテスト
func testCalc() {
let disposeBag = DisposeBag()
let viewModel = ViewModel()
let answerObserver = FulfillObserver(
expectationWithDescription("empty before calculation")) { $0 == "" }
viewModel.answerText
.bindTo(answerObserver)
.addDisposableTo(disposeBag)
waitForExpectationsWithTimeout(1.0, handler: nil)
answerObserver.reset(
expectationWithDescription("30 after calculation")) { $0 == "30" }
viewModel.number1ChangedAction.onNext("10")
viewModel.number2ChangedAction.onNext("20")
viewModel.calcAction.onNext()
waitForExpectationsWithTimeout(1.0, handler: nil)
}
Test
- 16. ViewModel改
let number1Text: Observable<String>
let number2Text: Observable<String>
let calcEnabled: Observable<Bool>
let answerText: Observable<String>
let number1ChangedAction: AnyObserver<String>
let number2ChangedAction: AnyObserver<String>
let calcAction: AnyObserver<Void>
private let _number1Text = BehaviorSubject<String>(value: "")
private let _number2Text = BehaviorSubject<String>(value: "")
private let _calcAction = PublishSubject<Void>()
init() {
number1Text = _number1Text.asObservable()
number2Text = _number2Text.asObservable()
number1ChangedAction = _number1Text.asObserver()
number2ChangedAction = _number2Text.asObserver()
calcAction = _calcAction.asObserver()
let numbers = Observable.combineLatest(number1Text, number2Text) { ($0, $1) }
calcEnabled = numbers
.map { (number1, number2) in
return !number1.isEmpty && !number2.isEmpty
}
answerText = _calcAction
.withLatestFrom(numbers)
.map { (number1, number2) in
let n1 = Int(number1) ?? 0
let n2 = Int(number2) ?? 0
return String(n1 + n2)
}
.startWith("")
}
ViewModel
外に公開した
入出力