How to hide keyboard when using SwiftUI?

66,720

Solution 1

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

Solution 2

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:

@main
struct 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)
    }
}

Solution 3

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 = false
    tapGesture.cancelsTouchesInView = false
    tapGesture.delegate = self //I don't use window as delegate to minimize possible side effects
    window?.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

Solution 4

I experienced this while using a TextField inside a NavigationView. This is my solution for that. It will dismiss the keyboard when you start scrolling.

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

Solution 5

@RyanTCB's answer is good; here are a couple of refinements that make it simpler to use and avoid a potential crash:

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

The 'bug fix' is simply that keyWindow!.endEditing(true) properly should be keyWindow?.endEditing(true) (yes, you might argue it can't happen.)

More interesting is how you can use it. For example, suppose you have a form with multiple editable fields in it. Just wrap it like this:

Form {
    .
    .
    .
}
.modifier(DismissingKeyboard())

Now, tapping on any control that itself doesn't present a keyboard will do the appropriate dismiss.

(Tested with beta 7)

Share:
66,720
Hitesh Surani
Author by

Hitesh Surani

Highly experienced and skilled Developer with a strong current background in industry technology. Proven ability to work independently or as part of a team. Adept at technical troubleshooting and anticipating future potential problems. Superior proficiency with the latest XCode IDE, VSCode and iPhone SDK Profound experience with Swift, Flutter, React Native, Objective-C and SwiftUI Strong record developing apps for iPad and iPhone with multi-language Excellent critical thinking and problem-solving skills Depth knowledge of web service integration for JSON and XML Strong OOP and designing skills with Autolayout and Animation Worked on bug tracking tools like Jira Good knowledge of interaction with web service APIs Excellent R&amp;D work quality Reaching out to me: LinkedIn, Github, Email &amp; Skype

Updated on July 08, 2022

Comments

  • Hitesh Surani
    Hitesh Surani almost 2 years

    How to hide keyboard using SwiftUI for below cases?

    Case 1

    I have TextField and I need to hide the keyboard when the user clicks the return button.

    Case 2

    I have TextField and I need to hide the keyboard when the user taps outside.

    How I can do this using SwiftUI?

    Note:

    I have not asked a question regarding UITextField. I want to do it by using SwifUI.TextField.

  • LinusGeffarth
    LinusGeffarth over 4 years
    .keyWindow is now deprecated. See Lorenzo Santini's answer.
  • LinusGeffarth
    LinusGeffarth over 4 years
    .keyWindow is now deprecated. See Lorenzo Santini's answer.
  • LinusGeffarth
    LinusGeffarth over 4 years
    Also, .tapAction has been renamed to .onTapGesture
  • Davide
    Davide over 4 years
    The force parameter is not used. It should be { $0.endEditing(force)}
  • Nalov
    Nalov over 4 years
    This gives another problem - i have a picker in the Form{} alongside of textfield, it became unresponsive. I didn't find a solution to it using all the answers in this topic. But your answer is a good for dismissing keyboard with a tap elsewhere - if you do not use pickers.
  • Nalov
    Nalov over 4 years
    hello. my code ``` var body: some View { NavigationView{ Form{ Section{ TextField("typesomething", text: $c) } Section{ Picker("name", selection: $sel) { ForEach(0..<200){ Text("(self.array[$0])%") } } } ``` The keyboard is dismissed when tapping elsewhere, but the picker became unresponsive. I didn't find a way to make it work.
  • Dim Novo
    Dim Novo over 4 years
    Hi again, at the moment I have two solutions: the first - is to use the native keyboard dismissed on the return button, the second - is to change the tapping handling slightly(aka 'костыль') - window.rootViewController = UIHostingController(rootView: contentView.onTapGesture(count: 2, perform: { window.endEditing(true) }) ) Hope this helps you...
  • Nalov
    Nalov over 4 years
    Hello. Thank you. The second way solved it. I am using numeric pad, so users can enter only numbers, it does not have return key. Dismissing with tapping was what i was searching.
  • Cui Mingda
    Cui Mingda over 4 years
    this will cause list can not be navigate.
  • Yarm
    Yarm over 4 years
    Hmmm - tapping on other controls no longer registers. The event is swallowed.
  • Yarm
    Yarm over 4 years
    Can the keyboard be dismissed when an alternate control becomes active? stackoverflow.com/questions/58643512/…
  • Feldur
    Feldur over 4 years
    I can't replicate that - it's still working for me using the latest drops from Apple as of 11/1. Did it work and then just stop working for you, or ??
  • Cinn
    Cinn over 4 years
    Combining with @DimNovo 's answer this works great (may solve the pickers issue didn't try): window.rootViewController = UIHostingController(rootView: contentView.onTapGesture { UIApplication.shared.sendAction(#selector(UIView.resignFirst‌​Responder), to: nil, from: nil, for: nil) })
  • Daniel Ryan
    Daniel Ryan over 4 years
    Still a flicker using this method.
  • Joseph Astrahan
    Joseph Astrahan over 4 years
    Is there a way to do this without the whitebackground, I'm using spacers and I need it to detect a tap gesture on the spacer. Also the white background strategy creates a problem on the newer iPhones where there is extra screen space above now. Any help appreciated!
  • Joseph Astrahan
    Joseph Astrahan over 4 years
    I posted an answer which improves upon your design. Feel free to make edits to your answer if you want I do not care for credit.
  • Joseph Astrahan
    Joseph Astrahan over 4 years
    My question now is how do you trigger a commit on a textfield when you get the keyboard to go away this way? currently the 'commit' only triggers if you hit the return key on the iOS keyboard.
  • Albert
    Albert over 4 years
    If you have a DatePicker in the form, then the DatePicker will not be shown any more
  • Feldur
    Feldur over 4 years
    @Albert - that's true; to use this approach, you'll have to break down where items are decorated with DismissingKeyboard() to a finer grained level that applies to the elements that should dismiss and avoids the DatePicker.
  • np2314
    np2314 over 4 years
    Usage of this code will reproduce the warning Can't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
  • Feldur
    Feldur about 4 years
    @np2314 Can you post your code that presents the problem? I checked with my app using this technique, and as of today (2/3) it's working.
  • Dan Barclay
    Dan Barclay about 4 years
    This is a superior approach to any method SwiftUI offers for handling keyboard resignation. Thank you
  • Imthath
    Imthath about 4 years
    This answer should be at the top. Other answers fail when there are other controls in the view.
  • Ravindra_Bhati
    Ravindra_Bhati about 4 years
    what's mean of eraseToAny()
  • Roland Lariotte
    Roland Lariotte about 4 years
    It works perfectly but dismisses the keyboard and shows it again when going from a TextField to another one.
  • Mikhail
    Mikhail about 4 years
    @RolandLariotte updated answer to fix this behaviour, look at new implementation of AnyGestureRecognizer
  • keegan3d
    keegan3d about 4 years
    This worked great, I used it slightly differently and had to be sure it was called on the main thread.
  • keegan3d
    keegan3d about 4 years
    This worked great, I used it slightly differently and had to be sure it was called on the main thread.
  • user3687284
    user3687284 about 4 years
    Perfect solution. Superior to anything I have tried, yet. All other solutions I tried had side effects, such that I could not move rows any more or normal dragging of the list was compromised. Thank you!!
  • Tarek Hallak
    Tarek Hallak about 4 years
    This will lead onDelete (swipe to delete) to s strange behavior.
  • Pasta
    Pasta almost 4 years
    Awesome answer. Works flawlessly. @Mikhail actually interested to know how do you remove the gesture recognizer specifically for some textfields (I built an autocomplete with tags, so everytime I tap an element in the list, I don't want this specific textfield to lose focus)
  • Mikhail
    Mikhail almost 4 years
    @Pasta in this case I would recommend to make an empty protocol like Undismissing, and implement it for any view you want (like UITextField, or your autocompletion view). And update touchesBegan method to check wether touched view confroms to this protocol, instead of checking for only UITextField/UITextView.
  • Mike Lyons
    Mike Lyons almost 4 years
    this works well except it doesn't seem to allow me to increment values via a button after i use this keyboard
  • Nils
    Nils almost 4 years
    This does not work for a Form view with Textfields. Form doesn't show up.
  • Peanutsmasher
    Peanutsmasher almost 4 years
    I assume, 2. is missing - class AnyGestureRecognizer: UIGestureRecognizer { - at the top. However, your implementation does not dismiss the keyboard when an UITextView via UIViewRepresentable is currently selected and somewhere outside the UITextView is being clicked. Should it? Can you please clarify where in 3. the code has to be added?
  • Maksym Rohovskoi
    Maksym Rohovskoi almost 4 years
    @randomcontrol haven't you received this error in #4 'Redundant conformance of SceneDelegate to protocol UIGestureRecognizerDelegate'? If not, do you have any idea why this happens ?
  • randomcontrol
    randomcontrol almost 4 years
    @Maksym Rohovskoi yes, I got that too. That's because when you do step 3 xcode tells you that you have to add the UIGestureRecognizerDelegate protocol to your "class SceneDelegate", which you tell xcode to do. But afterwards you add the extension which again conforms to that -> "extension SceneDelegate: UIGestureRecognizerDelegate" and then you have to remove ": UIGestureRecognizerDelegate" from "class SceneDelegate" again ;-)
  • Teo Sartori
    Teo Sartori almost 4 years
    There is no hideKeyboardOnTap modifier in iOS14
  • pawello2222
    pawello2222 almost 4 years
    @Mikhail Your solution is really good, but it ends editing not only for the keyboard input. I have issues when trying to select some text - I cannot change the selection. Every time I try to move a cursor (to expand the selection) the selection disappears. Are you able to modify your action:#selector(UIView.endEditing) to only hide keyboard and not interfere with text selection?
  • P Kuijpers
    P Kuijpers over 3 years
    Amazing all-purpose solution! Other solutions either disable taps on other views, or require you to implement it for each window. This is simple and to the point. --- ps. I moved step 3 to a method in the SceneDelegate extension: func closeSoftKeyboardOnAnyGesture(window: UIWindow) which is called in func scene, to keep the pieces of code together.
  • glassomoss
    glassomoss over 3 years
    this solution is actually great, but after using it for like 3 months, unfortunately I've found a bug, directly caused by this kind of hack. please, be aware of same happening to you
  • MichaelMao
    MichaelMao over 3 years
    UIApplication has no endEditing agagin, use 'UIApplication.shared.sendAction(#selector(UIResponder.resig‌​nFirstResponder), to: nil, from: nil, for: nil)'
  • Jessy
    Jessy over 3 years
  • Ahmadreza
    Ahmadreza over 3 years
    It's depreciated on iOS 13+
  • Dom
    Dom over 3 years
    Fantastic answer! I wonder how this will be implemented with iOS 14 without the scenedelegate?
  • pawello2222
    pawello2222 over 3 years
    @DominiqueMiller I adapted this solution for iOS 14 here.
  • Dom
    Dom over 3 years
    @pawello2222 Thanks!
  • carlosobedgomez
    carlosobedgomez over 3 years
    This should be on top because keeps in mind the new SwiftUI lifecycle.
  • Gary
    Gary over 3 years
    This works great. However if I double tap in a text field, instead of selecting the text the keyboard now disappears. Any idea how I can allow double tap for selection?
  • pawello2222
    pawello2222 over 3 years
    @Gary In the bottom extension you can see the line with the comment set to false if you don't want to detect tap during other gestures. Just set it to return false.
  • Gary
    Gary over 3 years
    Setting it to false works, but then the keyboard also doesn’t dismiss if someone long presses or drags or scrolls outside the text area. Is there some way to set it to false only for double clicks (preferably double clicks inside the text field, but even just all double clicks would do).
  • Gary
    Gary over 3 years
    To answer my own question, I set it back to true, and then I set tapGesture= AnyGestureRecognizer(... ) that Mikhail created in his answer rather than tapGesture=UITapGestureRecognizer(...). This allows double taps to select text within the text field while also allowing various gestures to hide the keyboard outside the text field.
  • DanielZanchi
    DanielZanchi over 3 years
    This is nice, but what about the tap?
  • Alienbash
    Alienbash over 3 years
    Maybe it's also worth noticing that UIApplication is part of UIKit, so one needs to import UIKit.
  • ElKePoN
    ElKePoN over 3 years
    Best solution for swiftui 2.0, however, there is a crash when double clicking on text fields
  • ElKePoN
    ElKePoN over 3 years
    @Gary I get AnyGestureRecognizer can you share the code?
  • Hasaan Ali
    Hasaan Ali over 3 years
    Awesome simple solution! I used this technique to hide keyboard whenever user taps anywhere outside the text field. See stackoverflow.com/a/65798558/1590911
  • Fero
    Fero about 3 years
    Dismissing keyboard this way should be discouraged because it acts on app's global view layout. The correct way should be to only dismiss in areas that should be tap-dismissible. Tapping on navigation bar should not dismiss keyboard. Tapping on scroll view should. It comes down to user experience but in general this global-tap-gesture approach is prone to unwanted side effects
  • pawello2222
    pawello2222 about 3 years
    @Ferologics But what should be tap-dismissible? IMHO tapping on navigation bar should dismiss keyboard. And actually as you can see from other answers, you're more likely to encounter issues when attaching gestures to single controls than when adding a global simultaneous gesture. But again it would all be way easier if we had a native approach.
  • Dan Fu
    Dan Fu almost 3 years
    It's actually easy to understand why it works
  • Roland Lariotte
    Roland Lariotte almost 3 years
    This code disable other touch actions on the View.
  • Roland Lariotte
    Roland Lariotte almost 3 years
    This code make cancelsTouchesInView = true. You can not tap on button that are present on the View.
  • Roland Lariotte
    Roland Lariotte almost 3 years
    This warning comes up when using this code 'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead.
  • pawello2222
    pawello2222 almost 3 years
    @RolandLariotte The iOS 14 solution will continue to work in iOS 15 (as confirmed here). However, iOS 15 gives us new, more native solutions for hiding keyboard - please see the updated answer.
  • Roland Lariotte
    Roland Lariotte almost 3 years
    @pawello2222 Unfortunately, the iOS 15 solution do not allow you to tap outside the keyboard to dismiss it.
  • pawello2222
    pawello2222 almost 3 years
    @RolandLariotte Assuming you use iOS, you can do guard let window = (connectedScenes.first as? UIWindowScene)?.windows.first else { return } to silence the warning. It will behave exactly the same as the original solution.
  • Peacemoon
    Peacemoon almost 3 years
    eraseToAnyView
  • Galen Smith
    Galen Smith over 2 years
    I used the iOS 14 (swiftUI 2) example below using @main with the 2 extensions. Are you saying I have to throw out all that code to implement the same thing in iOS 15? There isn't a simple fix to close the keyboard when tapping anywhere to close the keyboard?
  • Joe Scotto
    Joe Scotto over 2 years
    @GalenSmith No, I'm saying that I tested the solution I posted in iOS15. But it should work in iOS14, 13, etc with some minor changes to naming. I think specifically .onTapGesture is different
  • Lukasz D
    Lukasz D over 2 years
    Use Color(UIColor.systemBackground) instead of Color.white
  • Redar
    Redar over 2 years
    By far, the best solution showing all necessary steps both for iOS 14 and iOS 15 👍
  • Jimbo
    Jimbo over 2 years
    You'll note that this adds a bar to all text fields. See here: stackoverflow.com/questions/69478735/…
  • Przemyslaw Jablonski
    Przemyslaw Jablonski over 2 years
    @JoeScotto This is amazing, thank you!
  • Galen Smith
    Galen Smith over 2 years
    So in the iOS @Focused version, how would you dismiss the keyboard for a toggle or picker form field?
  • Liviu
    Liviu over 2 years
    I used to disregard messages recommending IQKeyboardManager because I thought "just another library". After some much struggle with the SwiftUI keyboard I finally came to implement it.
  • Alessandro Pace
    Alessandro Pace over 2 years
    Proper solution for +iOS15!
  • user14341201
    user14341201 about 2 years
    @pawello2222, did you find a solution using this but fix the issue to select some text? The keyboard closes when trying to select text. Thanks!
  • Ahmadreza
    Ahmadreza almost 2 years
    Simple and clean 👌but what if we use a phone pad keyboard 🤔
  • CD-3
    CD-3 almost 2 years
    Exactly what I was looking for! Thank you. This should be the top answer now.
  • axlrtr
    axlrtr almost 2 years
    The SwiftUI 3 version doesn't work for me. Copied the exact example. Only the Done button shows, and not the actual keyboard. Xcode 13.2.1.
  • NSSpeedForce
    NSSpeedForce almost 2 years
    This is a perfect answer. So clean and easy. Just put this in the .onTapGesture{} modifier of the view at the top level of the hierarchy and this solves the problem. Thank you
  • JAHelia
    JAHelia almost 2 years
    what if i have only one text field, should i create an enum for this text field ? sounds unreasonable.
  • pawello2222
    pawello2222 almost 2 years
    @JAHelia The .focused modifer accepts bool as well, so you don’t have to use enum if you only have one text field.