自分自身の理解を確認するためにRxSwiftのサンプルアプリを作ってみた。
サンプルコードはここ
この記事の大部分はRxのオペレーターやメソッドの説明になっています!(基礎的な部分が多め、自分もそこまで書けないので😅)でもこの記事をみて、是非最後に紹介する簡単なフォームバリデーションとボタンの制御から自分のプロジェクトに取り込んでみてください。
まだまだ少ないですが、これからどんどん更新していきます!
他にもこんなサンプルはどう?とかありましたらコメントお願いします
Sample 0 (MenuTableViewController)
サンプル一覧を表示するtableViewをRxで実装してみました!
let operators: Driver<[Operator]> = Driver.just([
Operator(title: "", description: ""),
....
])
operators.asDriver()
.drive(tableView.rx_itemsWithCellIdentifier("TitleCell")) { _, rxOperator, cell in
cell.textLabel?.text = rxOperator.title
}
.addDisposableTo(disposeBag)
tableView.rx_itemSelected
.withLatestFrom(operators) { indexPath, operators in
return (operators[indexPath.row], indexPath)
}
.subscribeNext { [weak self] (rxOperator, indexPath) in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
switch indexPath.row {
case 0:
.....
default:
break
}
}
.addDisposableTo(disposeBag)
Driverについてはこの記事を確認してください。
ここではcellのタップされたindexPathが流れるストリームとoperatorsストリームをwithLatestFromというオペレーターを使ってがっちゃんこして、そのindexPathに該当するOperatorとindexPathをリターンしています!
・withLatestFromはcombineLatestと同じような働きをします。
単に他のストリームをくっつけたいだけなら以下のように書くだけでも大丈夫です。
.....
.withLatestFrom(ストリーム)
...subscribe...
Sample 1
ボタンがタップされた時にStringのストリームをくっつけ、それをtextViewにbindTo(Drive)するサンプルです。
override func viewDidLoad() {
super.viewDidLoad()
let description = Driver<String>
.just(rxOperator.description)
rxButton.rx_tap.asDriver()
.flatMap { _ in
return description
}
.drive(descriptionTextView.rx_text)
.addDisposableTo(disposeBag)
}
・flatMapは二つのストリームを一つのストリームに変化させます。
↓こんな感じ

サンプルコードではUIButtonのrx_tapを使ってタップごとにVoidが流れるストリームを作ります。そしてVoidが流れてきたらflatMapでdescriptionというStringのストリームをつなげています!
画像でみると ○--- >の方がVoidのストリームにあたり、□---|->の方がStringのストリームにあたります。
・justは特定のタイプのobservableを一つ排出するストリームを作ります。

Sample 2
5秒ごとにenumで定義されているoperatorsをランダムにラベルに表示するサンプルです。
override func viewDidLoad() {
super.viewDidLoad()
enum operators: UInt32 {
case rx_sentMessage
case map
case interval
case of
case merge
}
let firstTrigger = Driver
.just(())
let intervalTrigger = Driver<Int>
.interval(5.0)
.map { _ in }
let mergedTrigger = Driver
.of(
firstTrigger,
intervalTrigger
)
.merge()
let operatorName = mergedTrigger.asDriver()
.map { _ in String(operators(rawValue: arc4random_uniform(5))!) }
operatorName
.drive(operatorLabel.rx_text)
.addDisposableTo(disposeBag)
Driver
.just(rxOperator.description)
.drive(descriptionTextView.rx_text)
.addDisposableTo(disposeBag)
}
・mapはストリームから排出されたアイテム一つ一つになんらかしらの関数を適用させて形を変えます!(説明すごく下手)
サンプルないではmap { _ in }で全てのアイテムをVoidに変形させて返していて、下の図では各アイテムを×10しています。
・intervalは指定されたタイムスパンで0から順番にIntを排出するストリームを作ります

このサンプルではmap { _ in }を使ってIntからVoidを返すようにしているので、毎5秒ごとにVoidを排出しています。
・merge()は複数のストリームを時系列に合わせて合体させます。
このサンプルの場合はfirstTriggerとintervalTriggerを合わせてmergedTriggerにしています!
これを使えば複数のボタンを一度に制御したりできるようになります。
Sample 3
concatを使ってviewWillAppearのタイミングで次のストリームを合体させて、"concat発動"をmapで返してtextLabelにDriveしています!(Sampleとしてなりたっていないのですがconcatとかについて説明させてください。。。)
rx_sentMessage(#selector(UIViewController.viewWillAppear(_:)))
.map { _ in }
.bindTo(viewWillAppearTrigger)
.addDisposableTo(disposeBag)
let printText = Observable
.just(())
//ここもUIに関するところなので本来はDriverの方が向いている。が、Observable版もサンプルとして残しておきます。
Observable
.of(
Observable.never().takeUntil(viewWillAppearTrigger),
printText
)
.concat()
.map { _ in "concat発動" }
.doOnNext { text in
print(text)
}
.bindTo(textLabel.rx_text)
.addDisposableTo(disposeBag)
Driver
.just(rxOperator.description)
.drive(descriptionTextView.rx_text)
.addDisposableTo(disposeBag)
・ concatは一つ目のストリームがonCompletedすると2つ目のストリームを一つ目の前に合体させます!
このサンプルではObservable.never().takeUntil(ストリーム)を使ってnever()という何も流れてこないストリームをtakeUntil()を使ってonCompletedしています!
↓takeUntil() 指定したストリームに最初のObservableアイテムが流れてきた時に、そのストリームをonCompletedにする!
下の例だと0が来たタイミングで上のストリームが終了している。
Sample 4
最後に今までにでてきたオペレーターを駆使して、簡単なフォームバリデーションを実装してみました!多分RxSwiftのサンプルでは一番基礎的!
override func viewDidLoad() {
super.viewDidLoad()
let description = Driver
.just(rxOperator.description)
let usernameValidation = usernameTextField.rx_text.asDriver()
.map { [weak self] username -> Bool in
return username.characters.count > self?.minWordCount
}
let passwordValidation = passwordTextField.rx_text.asDriver()
.map { [weak self] password -> Bool in
return password.characters.count > self?.minWordCount
}
usernameValidation
.drive(usernameValidationLabel.rx_hidden)
.addDisposableTo(disposeBag)
passwordValidation
.drive(passwordValidationLabel.rx_hidden)
.addDisposableTo(disposeBag)
let signUpButtonEnabled = [usernameValidation, passwordValidation]
.combineLatest { !$0.contains(false) }
signUpButtonEnabled
.drive(signUpButton.rx_enabled)
.addDisposableTo(disposeBag)
signUpButtonEnabled
.driveNext { [weak self] valid in
self?.signUpButton.backgroundColor = valid ?
UIColor(red: 135/255, green: 132/255, blue: 200/255, alpha: 1) : UIColor(red: 135/255, green: 132/255, blue: 200/255, alpha: 0.5)
}
.addDisposableTo(disposeBag)
signUpButton.rx_tap.asDriver()
.flatMap { _ in
return description
}
.drive(descriptionTextView.rx_text)
.addDisposableTo(disposeBag)
}
簡単に解説すると↓
let usernameValidation = usernameTextField.rx_text.asDriver()
.map { [weak self] username -> Bool in
return username.characters.count > self?.minWordCount
}
let passwordValidation = passwordTextField.rx_text.asDriver()
.map { [weak self] password -> Bool in
return password.characters.count > self?.minWordCount
}
上記のコードでユーザーネームとパスワードテキストに入っている文字数が最低文字数を超えているかをまず判断します。
なのでこの二つのusernameValidationとpasswordValidationはBoolになります。
そして次にこの二つの値を使って、「文字数を超えていません!」ラベルをrx_hiddenを使って制御します!
今回はUIの部分なのでDriveを使ってバインドしています!
↓この部分
usernameValidation
.drive(usernameValidationLabel.rx_hidden)
.addDisposableTo(disposeBag)
passwordValidation
.drive(passwordValidationLabel.rx_hidden)
.addDisposableTo(disposeBag)
次にボタンの制御です。
let signUpButtonEnabled = [usernameValidation, passwordValidation]
.combineLatest { !$0.contains(false) }
signUpButtonEnabled
.drive(signUpButton.rx_enabled)
.addDisposableTo(disposeBag)
次にcombineLatestを使ってusernameValidationとpasswordValidationを合体させます!そしてその結果を
rx_enabledにバインドして、textFieldが両方埋まった状態ではないとボタンをおせなくしてあります!
最後にボタンをタップした処理をかいて簡単なフォームバリデーションの完成です!
なにか「ーーーのサンプルがみてみたい!」みたいな意見があったら是非お願いしますm(_ _)m
是非この機会にReactive Programmingを自身のプロジェクトにいれてみてください!!