はじめに
UITextField はファーストレスポンダになるとキーボードを表示し、ファーストレスポンダをやめるとキーボードを非表示にする性質があります。
そのため、キーボードの表示・非表示は、ファーストレスポンダであるか否かで制御することになります。
// UITextField をファーストレスポンダにする(その結果、キーボードが表示される)
textField.becomeFirstResponder()
// UITextField のファーストレスポンダをやめる(その結果、キーボードが非表示になる)
textField.resignFirstResponder()
なのですが、ファーストレスポンダをやめる方法としては、他にも次のような方法があります。
-
.editingDidEndOnExit
を Target-Action に追加する -
endEditing(_ force: Bool)
を呼び出す
これらの方法は resignFirstResponder()
と何が違うのか?
気になったので、調べてみました。
環境
- Xcode Version 10.1 (10B61)
- Simlators iOS 12.1 (16B91)
.editingDidEndOnExit
.editingDidEndOnExit
は Return キーの入力によってファーストレスポンダをやめる場合に使用します。
サンプルコード
次のようなコードで、UITextField に Target-Action を追加します。
import UIKit
final class ViewController: UIViewController {
@IBOutlet private weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Target-Action を追加する
textField.addTarget(self, action: #selector(onExitAction(sender:)), for: .editingDidEndOnExit)
}
/// `.editingDidEndOnExit` イベントが送信されると呼ばれる
@objc func onExitAction(sender: Any) {
}
}
これで Return キーのタップでファーストレスポンダをやめることができます。
resignFirstResponder()
との関係
.editingDidEndOnExit
イベントが送信されると、UIKit の内部処理で resignFirstResponder()
が呼ばれるようになり、ファーストレスポンダをやめるようです。
次の場合は、Return キーをタップしても resignFirstResponder()
は呼ばれません。
-
addTarget(_:action:for:)
で.editingDidEndOnExit
を追加していない -
removeTarget(_:action:for:)
で.editingDidEndOnExit
をはずす
ファーストレスポンダをやめるのをやめる
オブジェクトの状態などによっては、キーボードを閉じることを抑止したいということも、もしかしたらあるかもしれません。
textFieldShouldReturn(_:)
で false
を返すと、.editingDidEndOnExit
イベントは送信されず、キーボードを表示したままにすることが出来ます。
import UIKit
final class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet private weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// UITextFieldDelegate を設定する
textField.delegate = self
textField.addTarget(self, action: #selector(onExitAction(sender:)), for: .editingDidEndOnExit)
}
@objc func onExitAction(sender: Any) {
// textFieldShouldReturn(_:) で `false` を返した場合は呼出されない
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// `false` を返した場合は、`.editingDidEndOnExit` イベントは送信されない
return false
}
}
また、textFieldShouldEndEditing(_:)
で false
を返すことで、キーボードを表示したままにすることが出来ます。
textFieldShouldEndEditing(_:)
は resignFirstResponder()
の後で呼ばれるので、Return キーのタップに限らず resignFirstResponder()
をキャンセルする目的で使用するとができます。
処理の流れ(概要)
以上の大まかな処理の流れを図に示すと、次のようになります。
endEditing(_:)
endEditing(_:)
はファーストレスポンダをやめるための便利なメソッドです。
リターンキーのタップではなく、コードから直接ファーストレスポンダをやめる場合に使用します。
サンプルコード
次のコードは、ビューをタップした際に、endEditing(_:)
でファーストレスポンダをやめる実装例です。
複数の UITextField が配置されているビューでは endEditing(_:)
を使用することで、
ファーストレスポンダの探索を省いて、ファーストレスポンダをやめることができるので便利です。
import UIKit
final class ViewController: UIViewController {
@IBOutlet private weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// `view` のタップジェスチャーを受け取る
view.addGestureRecognizer(UITapGestureRecognizer(
target: self,
action: #selector(onTapAction(sender:))))
}
/// タップジェスチャーのアクション
@objc func onTapAction(sender: UITapGestureRecognizer) {
// `view` のサブビュー階層内のファーストレスポンダを探索して、
// 該当オブジェクトの resignFirstResponder が呼び出される。
view.endEditing(true)
}
}
ちなみに、ファーストレスポンダの探索を省いて、ファーストレスポンダをやめるだけなら endEditing(_:)
の代わりに次のような方法でも実現できます。
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
sendAction(_:to:from:for:)
と endEditing(_:)
は、引数や戻り値が異なるので、意図に合わせて使い分けると良さそうです。
resignFirstResponder()
との関係
endEditing(_:)
を呼び出すと、UIKit の内部処理で resignFirstResponder()
が呼ばれるようになり、その結果、ファーストレスポンダをやめます。
渡す引数によって、resignFirstResponder()
を呼び出すまでの処理の流れが変わるようです。
endEditing(_:)
の引数と戻り値
endEditing(_:)
は、次のように Bool の引数を取り、Bool を返すメソッドです。
func endEditing(_ force: Bool) -> Bool
force パラメータの指定により、動作や戻り値が異なるようです。
引数に true
を設定した場合
- ビュー階層内のファーストレスポンダを探索して
resignFirstResponder()
を呼ぶ - 戻り値は常に
true
を返す
引数に false
を設定した場合
- まず、UITextFieldDelegate の
textFieldShouldEndEditing(_:)
を呼ぶ-
true
を返した場合は、resignFirstResponder()
を呼ぶ -
false
を返した場合はresignFirstResponder()
は呼ばれない
-
-
endEditing(_:)
の戻り値は上述のtextFieldShouldEndEditing(_:)
の戻り値に一致する
force パラメータに false
を設定することで、ファーストレスポンダをやめたかどうかを判定することができます。
import UIKit
final class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet private weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// UITextFieldDelegate を設定する
textField.delegate = self
view.addGestureRecognizer(UITapGestureRecognizer(
target: self,
action: #selector(onTapAction(sender:))))
}
/// タップジェスチャーのアクション
@objc func onTapAction(sender: UITapGestureRecognizer) {
// force パラメータに `false` を設定した場合は
// `textFieldShouldEndEditing(_:)` が `true` を返した場合のみ
// `resignFirstResponder()` が呼出される
let result = view.endEditing(false)
if !result {
// `resignFirstResponder()` は呼出されていないので
// キーボードは閉じない
}
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
// `false` を返すと `resignFirstResponder()` は呼出されない
return false
}
}
処理の流れ(概要)
endEditing(_:)
を呼び出した際の、大まかな処理の流れを図に示すと、次のようになります。
注意すべき点があります。
endEditing(_:)
に false
を渡して、textFieldShouldEndEditing(_:)
で true
を返すと、結果として、 textFieldShouldEndEditing(_:)
は2回呼ばれます。
また、この場合の endEditing(_:)
の戻り値は、textFieldShouldEndEditing(_:)
の1度目の呼び出しの戻り値に一致し、2度目の戻り値を無視するような挙動でした。
まとめ・感想
基本的には resignFirstResponder()
でファーストレスポンダをやめることが出来ますが、状況に応じた便利な仕組みが UIKit には提供されているみたいです。
- Return キーで閉じる場合は
.editingDidEndOnExit
- コードからファーストレスポンダをやめる場合は
endEditing(_ force: Bool)
が便利
ともに、UIKit 内部で resignFirstResponder()
を呼び出していて、resignFirstResponder()
がファーストレスポンダをやめるための、共通処理になっていることが想像できました。
UIKit を利用する立場としては、ファーストレスポンダをやめるための便利な仕組みが提供されていると思って、使い所に合わせて、これらを利用すれば良さそうです。