162
158

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RxSwiftのすぐに取り込める使用例をまとめてみた🏄🏻

Posted at

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

他にもこんなサンプルはどう?とかありましたらコメントお願いします

##Sample 0 (MenuTableViewController)
View ALL

サンプル一覧を表示するtableViewをRxで実装してみました!

MenuTableViewController
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に該当するOperatorindexPathをリターンしています!

withLatestFromcombineLatestと同じような働きをします。

Screen Shot 2016-04-23 at 4.20.20 PM.png

単に他のストリームをくっつけたいだけなら以下のように書くだけでも大丈夫です。

Sample
   .....
   .withLatestFrom(ストリーム)
   ...subscribe...

##Sample 1

ボタンがタップされた時にStringのストリームをくっつけ、それをtextViewにbindTo(Drive)するサンプルです。

Sample1ViewController.swift
    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は二つのストリームを一つのストリームに変化させます。
↓こんな感じ
Screen Shot 2016-04-23 at 4.27.03 PM.png

サンプルコードではUIButtonrx_tapを使ってタップごとにVoidが流れるストリームを作ります。そしてVoidが流れてきたらflatMapdescriptionというStringのストリームをつなげています!

画像でみると ○--- >の方がVoidのストリームにあたり、□---|->の方がStringのストリームにあたります。

justは特定のタイプのobservableを一つ排出するストリームを作ります。
Screen Shot 2016-04-23 at 5.34.19 PM.png

##Sample 2

5秒ごとにenumで定義されているoperatorsをランダムにラベルに表示するサンプルです。

Sample1ViewController.swift
    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しています。

Screen Shot 2016-04-23 at 9.46.41 PM.png

intervalは指定されたタイムスパンで0から順番にIntを排出するストリームを作ります
Screen Shot 2016-04-23 at 5.37.00 PM.png

このサンプルではmap { _ in }を使ってIntからVoidを返すようにしているので、毎5秒ごとにVoidを排出しています。

merge()は複数のストリームを時系列に合わせて合体させます。

このサンプルの場合はfirstTriggerintervalTriggerを合わせてmergedTriggerにしています!
これを使えば複数のボタンを一度に制御したりできるようになります。

Screen Shot 2016-04-23 at 9.17.40 PM.png

##Sample 3

concatを使ってviewWillAppearのタイミングで次のストリームを合体させて、"concat発動"をmapで返してtextLabelにDriveしています!(Sampleとしてなりたっていないのですがconcatとかについて説明させてください。。。)

Sample3ViewController.swift
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つ目のストリームを一つ目の前に合体させます!

Screen Shot 2016-04-23 at 9.35.38 PM.png

このサンプルではObservable.never().takeUntil(ストリーム)を使ってnever()という何も流れてこないストリームをtakeUntil()を使ってonCompletedしています!

takeUntil() 指定したストリームに最初のObservableアイテムが流れてきた時に、そのストリームをonCompletedにする!
下の例だと0が来たタイミングで上のストリームが終了している。

Screen Shot 2016-04-23 at 9.42.51 PM.png

##Sample 4

最後に今までにでてきたオペレーターを駆使して、簡単なフォームバリデーションを実装してみました!多分RxSwiftのサンプルでは一番基礎的!

Sample4ViewController.swift
    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)
    }

簡単に解説すると↓

wordCountValidation
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
            }

上記のコードでユーザーネームとパスワードテキストに入っている文字数が最低文字数を超えているかをまず判断します。
なのでこの二つのusernameValidationpasswordValidationBoolになります。

そして次にこの二つの値を使って、「文字数を超えていません!」ラベルをrx_hiddenを使って制御します!
今回はUIの部分なのでDriveを使ってバインドしています!
↓この部分

rx_hidden
usernameValidation
            .drive(usernameValidationLabel.rx_hidden)
            .addDisposableTo(disposeBag)

passwordValidation
            .drive(passwordValidationLabel.rx_hidden)
            .addDisposableTo(disposeBag)

次にボタンの制御です。

rx_enabled
let signUpButtonEnabled = [usernameValidation, passwordValidation]
            .combineLatest { !$0.contains(false) }

signUpButtonEnabled
            .drive(signUpButton.rx_enabled)
            .addDisposableTo(disposeBag)

次にcombineLatestを使ってusernameValidationpasswordValidationを合体させます!そしてその結果を
rx_enabledにバインドして、textFieldが両方埋まった状態ではないとボタンをおせなくしてあります!

最後にボタンをタップした処理をかいて簡単なフォームバリデーションの完成です!

なにか「ーーーのサンプルがみてみたい!」みたいな意見があったら是非お願いしますm(_ _)m
是非この機会にReactive Programmingを自身のプロジェクトにいれてみてください!!

162
158
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
162
158

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?