LoginSignup
5
7

More than 5 years have passed since last update.

RxCocoa の UITextField.rx.text を購読するとリターンキーでキーボードが閉じるようになる

Last updated at Posted at 2019-02-02

はじめに

RxCocoa で UITextField の入力値をイベントストリームとして受け取る場合、UITextField.rx.text を使います。

import UIKit
import RxSwift
import RxCocoa

final class ViewController: UIViewController {

    @IBOutlet private weak var textField: UITextField!

    private let bag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        textField.rx.text.subscribe().disposed(by: bag)
    }
}

このように UITextField.rx.textsubscribe() すると、入力値をイベントストリームとして受け取るだけでなく、なぜか、キーボードのリターンキーのタップでキーボードが閉じるようになりました。

output.gif

RxCocoa だけでなく、ReactiveCocoa でも似たような挙動になります。

ReactiveCocoa では、次のようにして UITextField の入力値をイベントストリームとして受け取る事ができ、やはり、キーボードのリターンキーのタップでキーボードが閉じるようになります。

import UIKit
import ReactiveSwift
import ReactiveCocoa

final class ViewController: UIViewController {

    @IBOutlet private weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        textField.reactive.continuousTextValues.observe { _ in }
    }
}

なぜキーボードが閉じるのか?気になったので、調べてみました。

UITextField.rx.text の実装の概要

RxCocoa の rx.text の実装 を追っていくと、次のような実装であることが分かりました。

  • UIControl.Events の .allEditingEvents.valueChanged をターゲットアクションに追加する
  • アクションが送信されると、UITextField の text プロパティを RxSwift のイベントストリームとして発行する

また、ReactiveCocoa の reactive.continuousTextValues の実装 では .allEditingEvents をターゲットアクションに追加するようでした。

リターンキーの入力でキーボードが閉じるようになる理由

RxCocoa に限らず、.allEditingEvents をターゲットアクションに追加すると、リターンキーの入力によってキーボードが閉じるようになります。

次のようなコードで確認することが出来ます。

import UIKit

final class ViewController: UIViewController {

    @IBOutlet private weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // リターンキーの入力でキーボードが閉じるようになる
        textField.addTarget(self, action: #selector(self.hoo(sender:)), for: .allEditingEvents)
    }

    @objc func hoo(sender: Any) {
    }
}

.allEditingEvents は UITextField の全ての編集イベントを含んでいるので、.editingDidEndOnExit も含んでいます。

.editingDidEndOnExit をターゲットアクションに追加すると、リターンキーの入力でファーストレスポンダをやめるようになります。これが本質的な理由です。

次のようなコードに置き換えると .editingDidEndOnExit イベントが送信されないので、リターンキーをタップしてもキーボードは閉じません。

import UIKit

final class ViewController: UIViewController {

    @IBOutlet private weak var textField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // リターンキーをタップしてもキーボードは閉じない
        var events = UIControl.Event.allEditingEvents
        events.subtract(.editingDidEndOnExit)
        textField.addTarget(self, action: #selector(self.hoo(sender:)), for: events)
    }

    @objc func hoo(sender: Any) {
    }
}

UITextField.rx.text でキーボードが閉じる処理を抑止するには

キーボードが閉じるのは便利ですし、通常はこのままでも問題ないと思いますが、次のように UITextFieldDelegate に適合することで、この挙動を抑止することも出来ます。

  • textFieldShouldReturn(_:)false を返す
  • textFieldShouldEndEditing(_:)false を返す

textFieldShouldReturn(_:)false を返した場合は、.editingDidEndOnExit が送信されなくなるので、リターンキーをタップしても UITextField.rx.text の next イベントが発行されず、キーボードを閉じることを抑止することができます。

final class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet private weak var textField: UITextField!

    private let bag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        textField.delegate = self

        textField.rx.text
            .bind(onNext: { print($0.debugDescription) })
            .disposed(by: bag)
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        // onNext は呼ばれない、キーボードも閉じない
        return false
    }
}

textFieldShouldEndEditing(_:).editingDidEndOnExit の送信後に呼ばれるので、リターンキーのタップで UITextField.rx.text の next イベントが発行され、かつキーボードが閉じるを抑止することができます。

final class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet private weak var textField: UITextField!

    private let bag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        textField.delegate = self

        textField.rx.text
            .bind(onNext: { print($0.debugDescription) })
            .disposed(by: bag)
    }

    func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
        // onNext は呼ばれるが、キーボードは閉じない
        return false
    }
}

ということで、意図に合わせて UITextFieldDelegate を実装すれば良さそうです。
RxCocoa ではなく、UIKit の仕様に依存した内容なので、ReactiveCocoa も同様の制御が可能です。

まとめ

  • rx.text を購読するとリターンキーのタップでキーボードが閉じるようになる
  • この挙動は .editingDidEndOnExit をターゲットアクションに追加した際の UIKit の標準的な挙動である
  • この挙動を抑止したい場合は UITextFieldDelegate を実装する
5
7
0

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
5
7