UITextFieldとRxSwiftを使ってバリデーションを実装する

概要

UITextFieldとUILabelをRxSwiftでバインディングする方法

ではUITextFieldとUILabelを接続してUITextFieldで入力中の値をリアルタイムでUILabelに更新していく方法が分かりました。 とは行ってもRxSwiftを使わない方法でもUITextFieldDelegateのメソッドを使うことと変わりません。

(もっと言えば実務で共同開発だとXcodeでデリゲートメソッドの検索を行うのでそれが使えなくなる方が面倒な気がしますけど。)

今回はさらにUITextFieldにバリデーションをかける方法について説明していきたいと思います。

開発環境について

Xcode: 10.1
Swift: 4.2
RxSwift: 4.4.0
RxCocoa: 4.4.0

ソースコードについて

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!
    
    var disposeBag = DisposeBag()
    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
        textField.rx.text.asObservable()
            .subscribe(onNext: { text in
            if text?.isEmpty == true {
                print("empty")
                self.textField.becomeFirstResponder()
            }
        }).disposed(by: disposeBag)
    }
}

こんな感じです。 UITextField の中身が空の時にだけコンソールに"empty"と表示されるソースコードとなります。

僕にとっては

        textField.rx.text.asObservable()
            .subscribe(onNext: { text in

の書き方が一番親近感があったりします(笑)。 .subscribeAndroidでも同じですのでAndroidでRxを書いた場合でも補完とかで出てきてサクサク書けます。

ここまで書ければRxSwiftの基本的な考え方が分かってくるのではないかなと思いました。

UITextFieldとUILabelをRxSwiftでバインディングする方法

概要

タイトルで「バインディング」という言葉を使いました。 しかしRxSwift初心者にとってまたiOS開発者にとって一番勘違いを起こしてしまうのがこの「バインディング」だと思ってます。 バインディングと格好良く行っていますが要するにSwiftのクロージャーの中で処理を繋げることと同じだと思っています。

今回はそれをUITextFieldとUILabelとの接続を通して理解を深めたいと思います。

開発環境について

Xcode: 10.1
Swift: 4.2
RxSwift: 4.4.0
RxCocoa: 4.4.0

バインディング

前回はUITextFieldのRxSwiftで他の@IBOutletと連携させない単独の処理を説明しました。 今回はUILabelと連携する方法についてです。

storyboardの内容は以前と変わりません。

UITextFieldのRxSwift処理の書き方について

ViewControlleの編集を行います。

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!
    
    var disposeBag = DisposeBag()
    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        textField.rx.text.orEmpty.asDriver()
            .drive(onNext: { [unowned self] text in
                // 入力するたびにこの処理が走る
                print("text: \(text)")
                print("isEditing: \(self.textField.isEditing)") // self.textField.isEditing はBool
                self.label.text = text
            })
            .disposed(by: disposeBag)
        
        textField.rx.text
            .bind(to: label.rx.text) // .bind の部分が .drive と同じです
            .disposed(by: disposeBag)
    }
}

上で2つ同じ処理をしているコードがあります。

        textField.rx.text.orEmpty.asDriver()
            .drive(onNext: { [unowned self] text in
                // 入力するたびにこの処理が走る
                print("text: \(text)")
                print("isEditing: \(self.textField.isEditing)") // self.textField.isEditing はBool
                self.label.text = text
            })
            .disposed(by: disposeBag)

        textField.rx.text
            .bind(to: label.rx.text) // .bind の部分が .drive と同じです
            .disposed(by: disposeBag)

この部分です。 僕にとっては同じ処理なのに2つの異なる書き方がある時点で勘違いが起きてしまう理由になりそうですが、 このように書くことができてしまいます。 .bindはどちらかというとRxを使ったAndroidに近い書き方かなという印象です。

APIから取得してごちゃごちゃとしたい場合は上の.drive(onNext: { [unowned self] text inなのかなと思います。

逆に言えば、 上記の2つの書き方のどちらかでUITextFieldとUILabelを連携できるようになるということでそれはそれで便利ではあります。

UITextFieldのRxSwift処理の書き方について

概要

UITextFieldのRxSwiftの書き方について調べてみました。 よくあるUILabelに連携してデータをバインディングする処理はまた別です。 超基本的な書き方について説明していきます。

開発環境について

Xcode: 10.1
Swift: 4.2
RxSwift: 4.4.0
RxCocoa: 4.4.0

UITextFieldの書き方について

RxSwiftのメリットであるDelegateメソッドを書く必要がないのでstoryboardとかでdelegateを接続させることが必要なくなります。 逆にdelegateを接続してViewControllerに準拠させるとアプリが落ちてしまう仕様のようなので接続させないように注意してください。

f:id:qed805:20190211115244p:plain

storyboardでの設定はこのように@IBOutletだけ接続させます。delegateは接続させないでください

ViewControllerのソースコードを以下のようになります。 コメントでどのタイミングでどの処理が走るのかを記載しています。

ViewController.swift

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!
    
    var disposeBag = DisposeBag()
    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        textField.rx.text.orEmpty.asDriver()
            .drive(onNext: { [unowned self] text in
                // 入力するたびにこの処理が走る
                print("text: \(text)")
                print("isEditing: \(self.textField.isEditing)") // self.textField.isEditing はBool
            })
            .disposed(by: disposeBag)
        
        
        textField.rx.controlEvent(.editingDidBegin).asDriver()
            .drive(onNext: { _ in
                // キーボードが表示された時に処理が走る
                print("editingDidBegin")
            })
            .disposed(by: disposeBag)
        
        textField.rx.controlEvent(.editingChanged).asDriver()
            .drive(onNext: { _ in
                // textFieldの値が変更されるたびに処理が走る
                print("editingChanged")
            })
            .disposed(by: disposeBag)
        
        textField.rx.controlEvent(.editingDidEnd).asDriver()
            .drive(onNext: { _ in
                // キーボードが閉じた時に処理が走る
                print("editingDidEnd")
            })
            .disposed(by: disposeBag)
        
        textField.rx.controlEvent(.editingDidEndOnExit).asDriver()
            .drive(onNext: { _ in
                // よくわからない
                print("editingDidEndOnExit")
            })
            .disposed(by: disposeBag)
        
        textField.rx.controlEvent(.valueChanged).asDriver()
            .drive(onNext: { _ in
                // よくわからない
                print("valueChanged")
            })
            .disposed(by: disposeBag)
    }
}

これでアプリを起動してUITextFieldを触ってみてください。

Xcodeのコンソールにprintの出力が表示されたら成功です。 UITextFieldのRxSwiftの書き方の基本形がこのようになりあとはバインディングやバリデーションが追加されると行った理解になります。

RxSwift, UIButtonでUILabelのラベルの文字を変更してみる

概要

こちらがよくQiitaで投稿されるRxSwiftを用いたUIButtonで「バインディング」するとか言われている技術の説明になります。(違いました)
バインディングとか行っているのにやっていることは今までと同じ概念(RxSwiftなしでの実装)になります。

開発環境について

Xcode: 10.1
Swift: 4.2
RxSwift: 4.4.0
RxCocoa: 4.4.0

Podfileについて

target 'RxPractice' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for RxPractice
    pod 'RxSwift', '~> 4.0'  # 追加する
    pod 'RxCocoa', '~> 4.0'  # 追加する
  target 'RxPracticeTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'RxPracticeUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

バインディングの処理について

ViewControllerにUILabelを載せる

スクリーンショット 2019-02-10 21.16.28.png

@IBOutlet接続をしましょう。

スクリーンショット 2019-02-10 21.17.17.png

実装前のソースコードはこちらになります。

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!
    
    var disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        button.rx.tap // tapが @IBAction func tapButton() {} に該当
            .subscribe(onNext: { _ in  // _ が (_ sender: AnyObject) の引数に該当
                // ここに@IBActionの処理を書いていく
                print("RxSwift")
            })
            .disposed(by: disposeBag)
    }
}

UIButtonとUILabelをバインディング(要は@IBActionでの処理)を書いていきます。

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var label: UILabel!
    
    var disposeBag = DisposeBag()
    var count = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        button.rx.tap // tapが @IBAction func tapButton() {} に該当
            .subscribe(onNext: { [weak self] _ in  // _ が (_ sender: AnyObject) の引数に該当
                // ここに@IBActionの処理を書いていく
                print("RxSwift")
                guard let self = self else { return }
                self.count += 1
                self.label.text = "\(self.count)"
            })
            .disposed(by: disposeBag)
    }
}

アプリをビルドしてボタンをタップしてみましょう。

スクリーンショット 2019-02-10 21.25.46.png

ラベルの文字が変われば成功です。

多分、ここまでが基本形ですが苦手意識のある人は3回くらい写経したらリズムが分かってくると思います。

RxSwift事始め。UIButtonのタップ実装

概要

RxSwiftを書いていて慣れてきましたので同僚にわかりやすく説明するためメモとして残してみます。
これをみてRxSwiftに苦手意識を持っているエンジニアの手助けができたらいいなと思いました。

開発環境について

Xcode: 10.1
Swift: 4.2
RxSwift: 4.4.0
RxCocoa: 4.4.0

RxSwiftのインストール

  1. まずはcocoapodをインストールしましょう。XXX.xcodeprojと同じディレクトリで以下を実行。
pod init

これで、Podfileが作成されます。 Podfileを開いてみると、下記のようになっています。

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'RxPractice' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for RxPractice

  target 'RxPracticeTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'RxPracticeUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
  1. RxSwiftとRxCocoaをインストールする

先ほど作成したPodfileの中にこのように編集します。

target 'RxPractice' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for RxPractice
    pod 'RxSwift',    '~> 4.0'  # 追加する
    pod 'RxCocoa',    '~> 4.0'  # 追加する

  target 'RxPracticeTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'RxPracticeUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
  1. 次のコマンドを実行します。
pod install

これでcocoapodのインストールが始まってRxSwiftとRxCocoaのインストールが完了します。

Analyzing dependencies
Downloading dependencies
Installing RxAtomic (4.4.0)
Installing RxCocoa (4.4.0)
Installing RxSwift (4.4.0)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `RxPractice.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There are 2 dependencies from the Podfile and 3 total pods installed.

[!] Automatically assigning platform `ios` with version `12.1` on target `RxPractice` because no platform was specified. Please specify a platform for this target in your Podfile. See `https://guides.cocoapods.org/syntax/podfile.html#platform`.

ディレクトリにXXX.xcworkspaceが作成されますのでそちらを開きます。

スクリーンショット 2019-02-10 20.47.09.png

これで基本的な設定が終わります。

UIButtonのRxSwift処理

RxSwiftを導入すると基本的に@IBActionが不要になるということだけ頭に入れてください。 @IBAction処理がRxになるという認識です。

  1. storyboardのVCの上にUIButtonを置く
  2. @IBOutlet接続をする
  3. RxSwiftでSubcribe(購読)する

こんな流れです。

  1. storyboardのVCの上にUIButtonを置く

スクリーンショット 2019-02-10 20.51.18.png

  1. @IBOutlet接続をする

ViewControler.swiftに下記のように編集します。

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
}

あとはstoryboardからUIButtonを接続します。

スクリーンショット 2019-02-10 20.55.01.png

  1. RxSwiftでSubcribe(購読)する

ViewControllerにRxSwiftRxCocoaをimportします。

import UIKit
import RxCocoa
import RxSwift

では@IBActionの処理を実装していきます。

class ViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    var disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        button.rx.tap // tapが @IBAction func tapButton() {} に該当
            .subscribe(onNext: { _ in  // _ が (_ sender: AnyObject) の引数に該当
                // ここに@IBActionの処理を書いていく
                print("RxSwift")
            })
            .disposed(by: disposeBag)
    }
}

これでアプリをビルドしてみましょう。

スクリーンショット 2019-02-10 21.04.14.png

ボタンをタップしてコンソールにRxSwiftとプリントされたら完成です。

まずこれが基本形だということを認識できればRxSwift事始めは完了だと思います。