How to hide keyboard when using SwiftUI? How to hide keyboard when using SwiftUI? ios ios

How to hide keyboard when using SwiftUI?


You can force the first responder to resign by sending an action to the shared application:

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

Now you can use this method to close the keyboard whenever you desire:

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()            }        }    }}

If you want to close the keyboard with a tap out, you can create a full screen white view with a tap action, that will trigger the 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()    }}


After a lot of attempts I found a solution that (currently) doesn't block any controls - adding gesture recognizer to UIWindow.

  1. If you want to close keyboard only on Tap outside (without handling drags) - then it's enough to use just UITapGestureRecognizer and just copy step 3:
  2. Create custom gesture recognizer class that works with any touches:

    class AnyGestureRecognizer: UIGestureRecognizer {    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {        if let touchedView = touches.first?.view, touchedView is UIControl {            state = .cancelled        } else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {            state = .cancelled        } else {            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. In SceneDelegate.swift in the func scene, add next code:

    let tapGesture = AnyGestureRecognizer(target: window, action:#selector(UIView.endEditing))tapGesture.requiresExclusiveTouchType = falsetapGesture.cancelsTouchesInView = falsetapGesture.delegate = self //I don't use window as delegate to minimize possible side effectswindow?.addGestureRecognizer(tapGesture)  
  4. Implement UIGestureRecognizerDelegate to allow simultaneous touches.

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

Now any keyboard on any view will be closed on touch or drag outside.

P.S. If you want to close only specific TextFields - then add and remove gesture recognizer to the window whenever called callback of TextField onEditingChanged


SwiftUI 3 (iOS 15+)

(Done button above the keyboard)

Starting with iOS 15 we can now use @FocusState to control which field should be focused (see this answer to see more examples).

We can also add ToolbarItems directly above the keyboard.

When combined together, we can add a Done button right above the keyboard. Here is a simple demo:

enter image description here

struct ContentView: View {    private enum Field: Int, CaseIterable {        case username, password    }    @State private var username: String = ""    @State private var password: String = ""    @FocusState private var focusedField: Field?    var body: some View {        NavigationView {            Form {                TextField("Username", text: $username)                    .focused($focusedField, equals: .username)                SecureField("Password", text: $password)                    .focused($focusedField, equals: .password)            }            .toolbar {                ToolbarItem(placement: .keyboard) {                    Button("Done") {                        focusedField = nil                    }                }            }        }    }}

SwiftUI 2 (iOS 14+)

(Tap anywhere to hide the keyboard)

Here is an updated solution for SwiftUI 2 / iOS 14 (originally proposed here by Mikhail).

It doesn't use the AppDelegate nor the SceneDelegate which are missing if you use the SwiftUI lifecycle:

@mainstruct TestApp: App {    var body: some Scene {        WindowGroup {            ContentView()                .onAppear(perform: UIApplication.shared.addTapGestureRecognizer)        }    }}extension UIApplication {    func addTapGestureRecognizer() {        guard let window = windows.first else { return }        let tapGesture = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))        tapGesture.requiresExclusiveTouchType = false        tapGesture.cancelsTouchesInView = false        tapGesture.delegate = self        window.addGestureRecognizer(tapGesture)    }}extension UIApplication: UIGestureRecognizerDelegate {    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {        return true // set to `false` if you don't want to detect tap during other gestures    }}

If you want to detect other gestures (not only tap gestures) you can use AnyGestureRecognizer as in Mikhail's answer:

let tapGesture = AnyGestureRecognizer(target: window, action: #selector(UIView.endEditing))

Here is an example how to detect simultaneous gestures except Long Press gestures:

extension UIApplication: UIGestureRecognizerDelegate {    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {        return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)    }}