#自分自身の理解を確認するためにRxSwiftのサンプルアプリを作ってみた。
サンプルコードはここ
この記事の大部分はRxのオペレーターやメソッドの説明になっています!(基礎的な部分が多め、自分もそこまで書けないので😅)でもこの記事をみて、是非最後に紹介する簡単なフォームバリデーションとボタンの制御から自分のプロジェクトに取り込んでみてください。
まだまだ少ないですが、これからどんどん更新していきます!
他にもこんなサンプルはどう?とかありましたらコメントお願いします
##Sample 0 (MenuTableViewController)
View ALL
サンプル一覧を表示する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を自身のプロジェクトにいれてみてください!!