57
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【iOS】UITextField のキーボードを閉じる処理について

Posted at

はじめに

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() をキャンセルする目的で使用するとができます。

処理の流れ(概要)

以上の大まかな処理の流れを図に示すと、次のようになります。

fc2-5.png

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(_:) を呼び出した際の、大まかな処理の流れを図に示すと、次のようになります。

fc2-6.png

注意すべき点があります。
endEditing(_:)false を渡して、textFieldShouldEndEditing(_:)true を返すと、結果として、 textFieldShouldEndEditing(_:) は2回呼ばれます。

また、この場合の endEditing(_:) の戻り値は、textFieldShouldEndEditing(_:) の1度目の呼び出しの戻り値に一致し、2度目の戻り値を無視するような挙動でした。

まとめ・感想

基本的には resignFirstResponder() でファーストレスポンダをやめることが出来ますが、状況に応じた便利な仕組みが UIKit には提供されているみたいです。

  • Return キーで閉じる場合は .editingDidEndOnExit
  • コードからファーストレスポンダをやめる場合は endEditing(_ force: Bool) が便利

ともに、UIKit 内部で resignFirstResponder() を呼び出していて、resignFirstResponder() がファーストレスポンダをやめるための、共通処理になっていることが想像できました。

UIKit を利用する立場としては、ファーストレスポンダをやめるための便利な仕組みが提供されていると思って、使い所に合わせて、これらを利用すれば良さそうです。

57
44
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
57
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?