概要
今回はRxSwiftを使って3つのラベルのの文字に制限を加えてその制限を満たしたらUIButtonをタップできるようにする実装を行います。
俗にいうバリデーション機能です。
使えそうなところは、
- メッセンジャーのメッセージの送信時のチェック
- ログイン時のバリデーションチェック
- ユーザー登録時の住所や名前、電話番号などの入力有無についてのチェック
などが代表例ですね。
開発環境について
Xcode: 10.1
Swift: 4.2
RxSwift: 4.4.0
RxCocoa: 4.4.0
storyboardについて
今回は3つのラベルの状態を監視するのでUILabel
は3つ
そのラベルの編集用にUITextField
を3つ
そして、ボタンを1つ
これらの部品をstoryboard に配置します。
配置はこのような感じになります。
@IBOutlet接続するのはUILabel3つとUITextField3つとUIButtonでそれぞれ接続させます。
そのため、ViewController.swiftのコードは次のようになります。
ViewController.swift
import UIKit
import RxCocoa
import RxSwift
class ViewController: UIViewController {
@IBOutlet weak var firstNameLabel: UILabel!
@IBOutlet weak var firstNameTextField: UITextField!
@IBOutlet weak var lastNameLabel: UILabel!
@IBOutlet weak var lastNameTextField: UITextField!
@IBOutlet weak var phoneNumberLabel: UILabel!
@IBOutlet weak var phoneNumberTextField: UITextField!
@IBOutlet weak var button: UIButton!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
self.button.isEnabled = false
self.button.setTitle("押せません", for: .disabled)
self.button.setTitle("押せます", for: .normal)
// firstNameのバリデーション用変数です。 Observable<Bool>なので 中身のvalueがbool型です。
let firstNameValid: Observable<Bool> = firstNameTextField.rx.text
.map{ text -> Bool in
text?.count ?? 0 >= 5 // 5文字以上であれば trueを返す
}
.share(replay: 1) // テキストを1文字入力すると1度だけmapの処理が走ります。
// lastNameのバリデーション用変数です。 Observable<Bool>なので 中身のvalueがbool型です。
let lastNameValid = lastNameTextField.rx.text
.map { text -> Bool in
text?.count ?? 0 >= 5 // 5文字以上であれば trueを返す
}
.share(replay: 1)
// phoneNumberのバリデーション用変数です。 Observable<Bool>なので 中身のvalueがbool型です。
let phoneNumberValid = phoneNumberTextField.rx.text
.map { text -> Bool in
text?.count ?? 0 >= 5 // 5文字以上であれば trueを返す
}
.share(replay: 1)
// 3つのバリデーション変数(Observable<Bool>)を組み合わせる
Observable.combineLatest(firstNameValid.asObservable(), lastNameValid.asObservable(), phoneNumberValid.asObservable())
.subscribe(onNext: { firstOk, lastOk, phoneOk in
self.button.isEnabled = firstOk && lastOk && phoneOk
})
.disposed(by: disposeBag)
firstNameTextField.rx.controlEvent(.editingDidEndOnExit).asDriver()
.drive(onNext: { _ in
print("editingDidEndOnExit")
self.firstNameTextField.resignFirstResponder()
})
.disposed(by: disposeBag)
lastNameTextField.rx.controlEvent(.editingDidEndOnExit).asDriver()
.drive(onNext: { _ in
print("editingDidEndOnExit")
self.lastNameTextField.resignFirstResponder()
})
.disposed(by: disposeBag)
phoneNumberTextField.rx.controlEvent(.editingDidEndOnExit).asDriver()
.drive(onNext: { _ in
print("editingDidEndOnExit")
self.phoneNumberTextField.resignFirstResponder()
})
.disposed(by: disposeBag)
}
}
ちなみに3つのバリデーション用の変数を作るための.share(replay: 1)
の返り値はRxSwift.Observable<Self.E>
となります。
簡単に言えば、Observableです。
今回のバリデーションの実装で重要な概念が2つですね。
Valid.swift
// firstNameのバリデーション用変数です。 Observable<Bool>なので 中身のvalueがbool型です。
let firstNameValid: Observable<Bool> = firstNameTextField.rx.text
.map{ text -> Bool in
text?.count ?? 0 >= 5 // 5文字以上であれば trueを返す
}
.share(replay: 1) // テキストを1文字入力すると1度だけmapの処理が走ります。
と
// 3つのバリデーション変数(Observable<Bool>)を組み合わせる
Observable.combineLatest(firstNameValid.asObservable(), lastNameValid.asObservable(), phoneNumberValid.asObservable())
.subscribe(onNext: { firstOk, lastOk, phoneOk in
self.button.isEnabled = firstOk && lastOk && phoneOk
})
.disposed(by: disposeBag)
この二つです。これらを3回くらい写経したらそのまま寝てしまってもいいくらいです。
RxSwiftはよくストリームとして「流れ」がある実装ができることが知られていますが、
僕は最初はこの流れと言うものがよく分かっていませんでした。
今、当時分かっていなかった自分に対して説明をするのならば、
この流れと言うのはSwiftのOptional
みたいなものだよと伝えていたと思います。
ストリームであるRxSwiftの中に機能(実装)を乗せたければObservable
で包まれた型
を作ればいいのです。
Observable
が観察可能
とかいう意味不明な言い方をしていますがObservable
は流れ
と一緒なのです。
このObservable
で包まれた型を使えばRx(リアクティブ)な実装ができるようになります。
そして、
Observable.combineLatest(firstNameValid.asObservable(), lastNameValid.asObservable(), phoneNumberValid.asObservable())
のcombineLatest
の部分がRxSwiftの実装になりこれは「組み合わせる」と言う意味にあります。
流れ的には
RxSwift -> combineLatest -> subscribe -> disposed
と言う流れになります。
上記の実装で複数のラベルのバリデーションを入力の都度確認してUIButtonの活性・不活性を制御できるようになりました。
以上で、基本的なRxSwiftの使い方は理解できるかなと思います。