UITextField.rx.textがイベントを発行するタイミング

UITextField.rx.textがイベントを発行するタイミングについてまとめました。

## イベント一覧
| 操作内容                                                               | UITextField.rx.textイベントの発生回数 | 同時に発行されるUIControl.Eventイベント |
|------------------------------------------------------------------------|---------------------------------------|-----------------------------------------|
| サブスクライブ                                                         | 1回                                   | なし                                    |
| テキストフィールドをタップしてフォーカスを当てる                              | 1回                                   | editingDidBegin                         |
| 画面タップによってフォーカスを外す                                     | 1回                                   | editingDidEnd                           |
| キーボードで1文字入力する                                              | 1回                                   | editingChanged                          |
| キーボードでリターンキーをタップする                                   | 2回                                   | editingDidEndOnExitとeditingDidEnd      |
| UITextField.textにコードから文字を代入する                             | 0回                                   | なし                                    |
| UITextField.textにコードから文字を代入し、valueChangedメッセージを送る | 1回                                   | valueChanged                            |

キーボードでリターンキーをタップした時、入力されている文字列が2回ストリームに流れてくるので、UITextField.rx.textをサブスクライブしてAPIリクエストを行うなどの処理をしている場合は余計な通信が発生しないよう注意が必要です。
下記で紹介しているコードを見ていただければと思いますが、こうした余計なイベントへの対応としてUITextField.isEditingプロパティが使えるかもしれません。

また、UITextField.textにコードから文字列を代入した場合、`UITextField.sendActions(for: .valueChanged)`を実行しないとUITextField.rx.textイベントが流れてこない点も気をつけましょう。

## 画面とコード
テストに使用した画面とコードを以下に載せておきます。

画面は一つのUITextFieldと二つのUIButtonで構成されています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/81715/7a531aa3-8faa-533c-2026-1746dca44590.png)

ViewControllerの実装は以下の通りです。
UITextField.rx.textプロパティおよびUITextFieldに関連するイベントをサブスクライブしています。
また、ViewControllerのviewプロパティにタップジェスチャーを追加しています。

```swift
class ViewController: UIViewController {
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var button: UIButton!
    @IBOutlet weak var button2: UIButton!

    private let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        let gesture = UITapGestureRecognizer()
        view.addGestureRecognizer(gesture)

        gesture.rx.event.asDriver()
            .drive(onNext: { [unowned self] event in
                print("tap event: \(event)")
                self.view.endEditing(true)
            })
            .disposed(by: disposeBag)

        textField.rx.text.orEmpty.asDriver()
            .drive(onNext: { [unowned self] text in
                print("text: \(text)")
                print("isEditing: \(self.textField.isEditing)")
            })
            .disposed(by: disposeBag)

        textField.rx.controlEvent(.editingDidBegin).asDriver()
            .drive(onNext: { _ in
                print("editingDidBegin")
            })
            .disposed(by: disposeBag)

        textField.rx.controlEvent(.editingChanged).asDriver()
            .drive(onNext: { _ in
                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)

        button.rx.tap.asDriver()
            .drive(onNext: { [unowned self] _ in
                print("button tapped")
                self.textField.text = "button"
            })
            .disposed(by: disposeBag)

        button2.rx.tap.asDriver()
            .drive(onNext: { [unowned self] _ in
                print("button2 tapped")
                self.textField.text = "button2"
                self.textField.sendActions(for: .valueChanged)
            })
            .disposed(by: disposeBag)
    }
}
```

## 実行ログ
画面操作内容とその時の出力ログです。

### 画面表示直後
```
text: 
isEditing: false
```

### テキストフィールドをタップしてフォーカスを当てた時
```
text:
isEditing: true
editingDidBegin
```

### 画面をタップしてテキストフィールドからフォーカスを外した時
```
tap event: <UITapGestureRecognizer: 0x6040001f3d00; state = Ended; view = <UIView 0x7f8f4a50a130>; target= <(action=eventHandler:, target=<_TtGC7RxCocoa13GestureTargetCSo22UITapGestureRecognizer_ 0x600000252690>)>>
text:
isEditing: false
editingDidEnd
```

### "a"という文字を入力した時
```
text: a
isEditing: true
editingChanged
```

### "a"という文字が入力された状態でリターンキーをタップした時
```
text: a
isEditing: true
editingDidEndOnExit
text: a
isEditing: false
editingDidEnd
```

### "Button"をタップした時
```
button tapped
```

### "Button2"をタップした時
```
button2 tapped
text: button2
isEditing: false
valueChanged
```