はじめに
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.text
を subscribe()
すると、入力値をイベントストリームとして受け取るだけでなく、なぜか、キーボードのリターンキーのタップでキーボードが閉じるようになりました。

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 を実装する