キーボードを閉じる・開閉を検知する

Published by @SoNiceInfo at 6/24/2020


テキスト入力で開いたキーボードを閉じる実装方法とキーボードの開閉を検知する方法を紹介します。

UIApplicationを拡張する

何気なく行っている「テキスト入力後に適当な場所をタップしてキーボードを閉じる」ですがiOSのキーボードのデフォルトの機能ではありません。
キーボードを閉じる機能はアプリ側で実装する必要があります。
さらにSwiftUIの標準機能では用意されていないのでUIApplicationを拡張して実装する必要があります。

UIApplicationを拡張してキーボードを閉じる関数を作成します。
関数closeKeyboard()を定義してその中でsendActionでセレクターに対してアクションを送信します。UIResponder.resignFirstRespondernilを設定することで、最上位のファーストレスポンダがnilとなります。
つまり、何もアクションしていない状態となりキーボードも閉じられます。

extension UIApplication {
    func closeKeyboard() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

onTapGestureでタップを検知する

なにかをタップしたことを検知するには対象のViewに対してonTapGestureを設定してアクションを呼び出します。
ここではZStackで作った黄色い背景にonTapGestureを仕込んでいます。
その中でUIApplication.shared.closeKeyboard()を呼び出すことで、黄色い背景をタップするとキーボードが閉じます。

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    @State var name: String = ""

    var body: some View {
        ZStack {
            Color.yellow
                .opacity(0.4)
                .edgesIgnoringSafeArea(.all)
                // ここが大事
                .onTapGesture {
                    UIApplication.shared.closeKeyboard()
                }
            
            VStack {
                Text("Result: ") + Text(name)
                TextField("Placeholder", text: $name)
                    .padding()
                    .border(Color.green, width: CGFloat(2))
            }
        }
    }
}

// ここが大事
extension UIApplication {
    func closeKeyboard() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
キーボードを閉じる画像

キーボードの開閉を検知する

キーボードの状態を検知して動作を実装する方法を紹介します。
キーボードに限らずなにかの動きを検知するにはonReceiveを使います。
onReceiveを使うとpublisherからのイベント発生通知を検知できるようになります。
JavaScriptでいうイベントリスナーみたいなものだなと思いました。

キーボードが開いたことはUIResponder.keyboardDidShowNotification、 閉じたことはUIResponder.keyboardDidHideNotificationでそれぞれ検知します。

.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
    // Write code for keyboard opened.
}.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in
    // Write code for keyboard closed.
}

以下にキーボード周りの通知をまとめます。

開くUIResponder.keyboardWillShowNotification
開いたUIResponder.keyboardDidShowNotification
閉じるUIResponder.keyboardWillHideNotification
閉じたUIResponder.keyboardDidHideNotification
変化するUIResponder.keyboardWillChangeFrameNotification
変化したUIResponder.keyboardDidChangeFrameNotification

これを使うと上記プログラムに追加してキーボードが閉じてるときはClosed, 開いてるときはOpenedと表示するプログラムが書けます。

//
//  ContentView.swift
//

import SwiftUI

struct ContentView: View {
    @State var name: String = ""
    @State var state: String = "Closed"

    var body: some View {
        ZStack {
            Color.yellow
                .opacity(0.4)
                .edgesIgnoringSafeArea(.all)
                // ここが大事
                .onTapGesture {
                    UIApplication.shared.closeKeyboard()
                }
            
            VStack {
                Text("Result: ") + Text(name)
                TextField("Placeholder", text: $name)
                    .padding()
                    .border(Color.green, width: CGFloat(2))
                Text("Keyboard: ") + Text(state)
            }
        }.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidShowNotification)) { _ in
            self.state = "Opened"
        }.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification)) { _ in
            self.state = "Closed"
        }
    }
}

// ここが大事
extension UIApplication {
    func closeKeyboard() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
キーボードを閉じる画像

その他

他のサイトではUIApplication.shared.keyWindow?.endEditing(true)を呼び出す方法が紹介されています。
この方法だとiOS13でUIApplication.shared.keyWindowが廃止になっているためおすすめできません。

参考

sendAction(_:to:from:for :)-UIApplication | Apple開発者用ドキュメント

    アプリをリリースしました!

    ToDoアプリ

    iCloudを利用したデバイス間データ共有、ダークモードに対応しています。

    リリースしたToDoアプリのスクリーンショット

    IPアドレス履歴保存アプリ

    取得したIPアドレスを確認、位置情報とともに保存できます。

    リリースしたIPアドレス保存アプリのスクリーンショット