web-dev-qa-db-ja.com

SwiftUIを使用しているときにキーボードを非表示にする方法は?

以下の場合にkeyboardを使用してSwiftUIを非表示にする方法は?

ケース1

私にはTextFieldがあり、ユーザーがkeyboardボタンをクリックしたときにreturnを非表示にする必要があります。

ケース2

私にはTextFieldがあり、ユーザーが外側をタップしたときにkeyboardを非表示にする必要があります。

SwiftUIを使用してこれを行うにはどうすればよいですか?

注:

私はUITextFieldについて質問していません。 SwifUITextField)を使用して実行します。

47
IMHiteshSurani

共有アプリケーションにアクションを送信することにより、ファーストレスポンダを強制的に辞任させることができます。

_extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
_

これで、このメソッドを使用して、いつでもキーボードを閉じることができます。

_struct ContentView : View {
    @State private var name: String = ""

    var body: some View {
        VStack {
            Text("Hello \(name)")
            TextField("Name...", text: self.$name) {
                // Called when the user tap the return button
                // see `onCommit` on TextField initializer.
                UIApplication.shared.endEditing()
            }
        }
    }
}
_

タップアウトでキーボードを閉じる場合は、endEditing(_:)をトリガーするタップアクションで全画面の白いビューを作成できます。

_struct Background<Content: View>: View {
    private var content: Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }

    var body: some View {
        Color.white
        .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
        .overlay(content)
    }
}

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

    var body: some View {
        Background {
            VStack {
                Text("Hello \(self.name)")
                TextField("Name...", text: self.$name) {
                    self.endEditing()
                }
            }
        }.onTapGesture {
            self.endEditing()
        }
    }

    private func endEditing() {
        UIApplication.shared.endEditing()
    }
}
_
47
rraphael

@RyanTCBの答えは良いです。次に、使用を簡単にし、潜在的なクラッシュを回避するためのいくつかの改良点を示します。

_struct DismissingKeyboard: ViewModifier {
    func body(content: Content) -> some View {
        content
            .onTapGesture {
                let keyWindow = UIApplication.shared.connectedScenes
                        .filter({$0.activationState == .foregroundActive})
                        .map({$0 as? UIWindowScene})
                        .compactMap({$0})
                        .first?.windows
                        .filter({$0.isKeyWindow}).first
                keyWindow?.endEditing(true)                    
        }
    }
}
_

「バグ修正」は、単にkeyWindow!.endEditing(true)keyWindow?.endEditing(true)であるべきだということです(そうです、それは起こり得ないと主張するかもしれません。)

より興味深いのは、それをどのように使用できるかです。たとえば、複数の編集可能なフィールドを含むフォームがあるとします。次のようにラップしてください:

_Form {
    .
    .
    .
}
.modifier(DismissingKeyboard())
_

これで、それ自体がキーボードを表示しないコントロールをタップすると、適切に終了します。

(ベータ7でテスト済み)

16
Feldur

'SceneDelegate.Swift'ファイルのSwiftUIは、次のコードを追加するだけです。onTapGesture {window.endEditing(true)}

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let contentView = ContentView()

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(
                rootView: contentView.onTapGesture { window.endEditing(true)}
            )
            self.window = window
            window.makeKeyAndVisible()
        }
    }

これは、アプリでキーボードを使用する各ビューに十分です...

13
Dim Novo

多くの試みの後、私は(現在のところ)コントロールをブロックしないソリューションを見つけました-UIWindowにジェスチャー認識機能を追加します。

  1. タップの外側でのみ(ドラッグを処理せずに)キーボードを閉じたい場合は、UITapGestureRecognizerを使用してステップ3をコピーするだけで十分です。
  2. 任意のタッチで機能するカスタムジェスチャレコグナイザクラスを作成します。

    class AnyGestureRecognizer: UIGestureRecognizer {
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
            state = .began
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
           state = .ended
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
            state = .cancelled
        }
    }
    
  3. SceneDelegate.Swift次のコードを追加:

    let tapGesture = AnyGestureRecognizer(target: window, action:#selector(UIView.endEditing))
    tapGesture.requiresExclusiveTouchType = false
    tapGesture.cancelsTouchesInView = false
    tapGesture.delegate = self //I don't use window as delegate to minimize possible side effects
    window.addGestureRecognizer(tapGesture)  
    
  4. UIGestureRecognizerDelegateを実装して、同時にタッチできるようにします。

    extension SceneDelegate: UIGestureRecognizerDelegate {
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            return true
        }
    }
    

これで、任意のビューのキーボードは、タッチするか、外側にドラッグすると閉じます。

追伸特定のTextFieldのみを閉じたい場合は、TextFieldのコールバックが呼び出されたときにジェスチャー認識機能をウィンドウに追加および削除しますonEditingChanged

12
Mikhail

keyWindowプロパティへのアクセスを必要としないキーボードを閉じる別の方法を見つけました。実際のところ、コンパイラは次を使用して警告を返します

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow'はiOS 13.0で廃止されました:複数のシーンをサポートするアプリケーションには使用しないでください。接続されているすべてのシーンでキーウィンドウが返されます。

代わりに私はこのコードを使用しました:

UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
11
Lorenzo Santini

NavigationView内でTextFieldを使用しているときにこれを経験しました。これが私の解決策です。スクロールを開始すると、キーボードが非表示になります。

NavigationView {
    Form {
        Section {
            TextField("Receipt amount", text: $receiptAmount)
            .keyboardType(.decimalPad)
           }
        }
     }
     .gesture(DragGesture().onChanged{_ in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)})
10
DubluDe

ユーザーのタップを検出するビューにこの修飾子を追加します

.onTapGesture {
            let keyWindow = UIApplication.shared.connectedScenes
                               .filter({$0.activationState == .foregroundActive})
                               .map({$0 as? UIWindowScene})
                               .compactMap({$0})
                               .first?.windows
                               .filter({$0.isKeyWindow}).first
            keyWindow!.endEditing(true)

        }
6
RyanTCB

keyWindowは廃止されたためです。

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}
5
msk

@Feldur(@RyanTCBに基づいていた)の回答を拡張して、onTapGesture以外のジェスチャーでキーボードを閉じることができる、より表現力があり強力なソリューションを示します。関数で必要なものを指定できますコール。

使用法

// MARK: - View
extension RestoreAccountInputMnemonicScreen: View {
    var body: some View {
        List(viewModel.inputWords) { inputMnemonicWord in
            InputMnemonicCell(mnemonicInput: inputMnemonicWord)
        }
        .dismissKeyboard(on: [.tap, .drag])
    }
}

またはAll.gesturesを使用します(Gestures.allCasesの砂糖だけですか????)

.dismissKeyboard(on: All.gestures)

コード

enum All {
    static let gestures = all(of: Gestures.self)

    private static func all<CI>(of _: CI.Type) -> CI.AllCases where CI: CaseIterable {
        return CI.allCases
    }
}

enum Gestures: Hashable, CaseIterable {
    case tap, longPress, drag, magnification, rotation
}

protocol ValueGesture: Gesture where Value: Equatable {
    func onChanged(_ action: @escaping (Value) -> Void) -> _ChangedGesture<Self>
}
extension LongPressGesture: ValueGesture {}
extension DragGesture: ValueGesture {}
extension MagnificationGesture: ValueGesture {}
extension RotationGesture: ValueGesture {}

extension Gestures {
    @discardableResult
    func apply<V>(to view: V, perform voidAction: @escaping () -> Void) -> AnyView where V: View {

        func highPrio<G>(
             gesture: G
        ) -> AnyView where G: ValueGesture {
            view.highPriorityGesture(
                gesture.onChanged { value in
                    _ = value
                    voidAction()
                }
            ).eraseToAny()
        }

        switch self {
        case .tap:
            // not `highPriorityGesture` since tapping is a common gesture, e.g. wanna allow users
            // to easily tap on a TextField in another cell in the case of a list of TextFields / Form
            return view.gesture(TapGesture().onEnded(voidAction)).eraseToAny()
        case .longPress: return highPrio(gesture: LongPressGesture())
        case .drag: return highPrio(gesture: DragGesture())
        case .magnification: return highPrio(gesture: MagnificationGesture())
        case .rotation: return highPrio(gesture: RotationGesture())
        }

    }
}

struct DismissingKeyboard: ViewModifier {

    var gestures: [Gestures] = Gestures.allCases

    dynamic func body(content: Content) -> some View {
        let action = {
            let forcing = true
            let keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
            keyWindow?.endEditing(forcing)
        }

        return gestures.reduce(content.eraseToAny()) { $1.apply(to: $0, perform: action) }
    }
}

extension View {
    dynamic func dismissKeyboard(on gestures: [Gestures] = Gestures.allCases) -> some View {
        return ModifiedContent(content: self, modifier: DismissingKeyboard(gestures: gestures))
    }
}

注意の言葉

allジェスチャーを使用すると競合する可能性があり、私はそれを解決する適切な解決策を思いついていないことに注意してください。

4
Sajjon

endEditingソリューションが@rraphaelのように指摘された唯一のソリューションのようです。
これまでに見た中で最もクリーンな例は次のとおりです。

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.keyWindow?.endEditing(force)
    }
}

そして、それをonCommit:

4
zero3nna

私は.onLongPressGesture(minimumDuration: 0)を使用することを好みます。これにより、別のTextViewがアクティブになったときにキーボードが点滅しません(.onTapGestureの副作用)。キーボードの非表示コードは再利用可能な機能です。

.onTapGesture(count: 2){} // UI is unresponsive without this line. Why?
.onLongPressGesture(minimumDuration: 0, maximumDistance: 0, pressing: nil, perform: hide_keyboard)

func hide_keyboard()
{
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
2
George Valkov

確認してください https://github.com/michaelhenry/KeyboardAvoider

KeyboardAvoider {}メインビューの上に表示されます。

KeyboardAvoider {
    VStack { 
        TextField()
        TextField()
        TextField()
        TextField()
    }

}
2
Michael Henry

ユーザーが外部をタップしたときにソフトウェアキーボードを非表示にする方法。 Viewコンテナ全体を検出するには、contentShapeonLongPressGestureとともに使用する必要があります。 onTapGestureへのフォーカスのブロックを回避するには、TextFieldが必要です。 onTapGestureの代わりにonLongPressGestureを使用できますが、NavigationBarアイテムは機能しません。

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

struct KeyboardAvoiderDemo: View {
    @State var text = ""
    var body: some View {
        VStack {
            TextField("Demo", text: self.$text)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .contentShape(Rectangle())
        .onTapGesture {}
        .onLongPressGesture(
            pressing: { isPressed in if isPressed { self.endEditing() } },
            perform: {})
    }
}
1

このメソッドを使用すると、キーボードを非表示スペーサー!

最初にこの関数を追加します(クレジット提供先:Casper Zandbergen、から SwiftUIはHStackのスペーサーをタップできません

_extension Spacer {
    public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View {
        ZStack {
            Color.black.opacity(0.001).onTapGesture(count: count, perform: action)
            self
        }
    }
}
_

次に、次の2つの関数を追加します(指定されたクレジット:rraphael、この質問から)

_extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}
_

以下の関数がViewクラスに追加されます。詳細については、rraphaelのこちらのトップアンサーを参照してください。

_private func endEditing() {
   UIApplication.shared.endEditing()
}
_

最後に、単に呼び出すことができます...

_Spacer().onTapGesture {
    self.endEditing()
}
_

これにより、スペーサー領域がキーボードを閉じるようになります。大きな白い背景ビューはもう必要ありません!

このextensionの手法を、現在サポートされていないTapGesturesをサポートする必要があるすべてのコントロールに仮説的に適用し、onTapGesture関数をself.endEditing()と組み合わせて呼び出して、あなたが望むどんな状況でもキーボード。

1
Joseph Astrahan

@Sajjonの答えに基づいて、ここでは、キーボードをタップ、長押し、ドラッグ、拡大、回転ジェスチャーを選択に応じて閉じることができるソリューションがあります。

このソリューションはXCode 11.4で動作しています

@IMHiteshSuraniによって要求された動作を取得するための使用法

struct MyView: View {
    @State var myText = ""

    var body: some View {
        VStack {
            DismissingKeyboardSpacer()

            HStack {
                TextField("My Text", text: $myText)

                Button("Return", action: {})
                    .dismissKeyboard(on: [.longPress])
            }

            DismissingKeyboardSpacer()
        }
    }
}

struct DismissingKeyboardSpacer: View {
    var body: some View {
        ZStack {
            Color.black.opacity(0.0001)

            Spacer()
        }
        .dismissKeyboard(on: Gestures.allCases)
    }
}

コード

enum All {
    static let gestures = all(of: Gestures.self)

    private static func all<CI>(of _: CI.Type) -> CI.AllCases where CI: CaseIterable {
        return CI.allCases
    }
}

enum Gestures: Hashable, CaseIterable {
    case tap, longPress, drag, magnification, rotation
}

protocol ValueGesture: Gesture where Value: Equatable {
    func onChanged(_ action: @escaping (Value) -> Void) -> _ChangedGesture<Self>
}

extension LongPressGesture: ValueGesture {}
extension DragGesture: ValueGesture {}
extension MagnificationGesture: ValueGesture {}
extension RotationGesture: ValueGesture {}

extension Gestures {
    @discardableResult
    func apply<V>(to view: V, perform voidAction: @escaping () -> Void) -> AnyView where V: View {

        func highPrio<G>(gesture: G) -> AnyView where G: ValueGesture {
            AnyView(view.highPriorityGesture(
                gesture.onChanged { _ in
                    voidAction()
                }
            ))
        }

        switch self {
        case .tap:
            return AnyView(view.gesture(TapGesture().onEnded(voidAction)))
        case .longPress:
            return highPrio(gesture: LongPressGesture())
        case .drag:
            return highPrio(gesture: DragGesture())
        case .magnification:
            return highPrio(gesture: MagnificationGesture())
        case .rotation:
            return highPrio(gesture: RotationGesture())
        }
    }
}

struct DismissingKeyboard: ViewModifier {
    var gestures: [Gestures] = Gestures.allCases

    dynamic func body(content: Content) -> some View {
        let action = {
            let forcing = true
            let keyWindow = UIApplication.shared.connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows
                .filter({$0.isKeyWindow}).first
            keyWindow?.endEditing(forcing)
        }

        return gestures.reduce(AnyView(content)) { $1.apply(to: $0, perform: action) }
    }
}

extension View {
    dynamic func dismissKeyboard(on gestures: [Gestures] = Gestures.allCases) -> some View {
        return ModifiedContent(content: self, modifier: DismissingKeyboard(gestures: gestures))
    }
}
0
Nicolas Mandica