How to hide keyboard when using SwiftUI?
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 ToolbarItem
s directly above the keyboard.
When combined together, we can add a Done
button right above the keyboard. Here is a simple demo:
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
.
- 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: -
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 } }
-
In
SceneDelegate.swift
in thefunc 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)
-
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)
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&D work quality Reaching out to me: LinkedIn, Github, Email & Skype
Updated on July 08, 2022Comments
-
Hitesh Surani almost 2 years
How to hide
keyboard
usingSwiftUI
for below cases?Case 1
I have
TextField
and I need to hide thekeyboard
when the user clicks thereturn
button.Case 2
I have
TextField
and I need to hide thekeyboard
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 usingSwifUI.TextField
. -
LinusGeffarth over 4 years
.keyWindow
is now deprecated. See Lorenzo Santini's answer. -
LinusGeffarth over 4 years
.keyWindow
is now deprecated. See Lorenzo Santini's answer. -
LinusGeffarth over 4 yearsAlso,
.tapAction
has been renamed to.onTapGesture
-
Davide over 4 yearsThe
force
parameter is not used. It should be{ $0.endEditing(force)}
-
Nalov over 4 yearsThis 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 over 4 yearshello. 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 over 4 yearsHi 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 over 4 yearsHello. 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 over 4 yearsthis will cause list can not be navigate.
-
Yarm over 4 yearsHmmm - tapping on other controls no longer registers. The event is swallowed.
-
Yarm over 4 yearsCan the keyboard be dismissed when an alternate control becomes active? stackoverflow.com/questions/58643512/…
-
Feldur over 4 yearsI 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 over 4 yearsCombining 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.resignFirstResponder), to: nil, from: nil, for: nil) })
-
Daniel Ryan over 4 yearsStill a flicker using this method.
-
Joseph Astrahan over 4 yearsIs 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 over 4 yearsI 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 over 4 yearsMy 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 over 4 yearsIf you have a DatePicker in the form, then the DatePicker will not be shown any more
-
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 over 4 yearsUsage 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 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 about 4 yearsThis is a superior approach to any method SwiftUI offers for handling keyboard resignation. Thank you
-
Imthath about 4 yearsThis answer should be at the top. Other answers fail when there are other controls in the view.
-
Ravindra_Bhati about 4 yearswhat's mean of
eraseToAny()
-
Roland Lariotte about 4 yearsIt works perfectly but dismisses the keyboard and shows it again when going from a TextField to another one.
-
Mikhail about 4 years@RolandLariotte updated answer to fix this behaviour, look at new implementation of AnyGestureRecognizer
-
keegan3d about 4 yearsThis worked great, I used it slightly differently and had to be sure it was called on the main thread.
-
keegan3d about 4 yearsThis worked great, I used it slightly differently and had to be sure it was called on the main thread.
-
user3687284 about 4 yearsPerfect 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 about 4 yearsThis will lead onDelete (swipe to delete) to s strange behavior.
-
Pasta almost 4 yearsAwesome 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 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 almost 4 yearsthis works well except it doesn't seem to allow me to increment values via a button after i use this keyboard
-
Nils almost 4 yearsThis does not work for a Form view with Textfields. Form doesn't show up.
-
Peanutsmasher almost 4 yearsI 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 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 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 almost 4 yearsThere is no hideKeyboardOnTap modifier in iOS14
-
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 over 3 yearsAmazing 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 infunc scene
, to keep the pieces of code together. -
glassomoss over 3 yearsthis 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 over 3 yearsUIApplication has no endEditing agagin, use 'UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)'
-
Jessy over 3 yearsProbably got confused: github.com/jeroenzonneveld/SwiftUIFormHelper/blob/develop/…
-
Ahmadreza over 3 yearsIt's depreciated on iOS 13+
-
Dom over 3 yearsFantastic answer! I wonder how this will be implemented with iOS 14 without the scenedelegate?
-
pawello2222 over 3 years@DominiqueMiller I adapted this solution for iOS 14 here.
-
Dom over 3 years@pawello2222 Thanks!
-
carlosobedgomez over 3 yearsThis should be on top because keeps in mind the new SwiftUI lifecycle.
-
Gary over 3 yearsThis 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 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 over 3 yearsSetting 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 over 3 yearsTo 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 over 3 yearsThis is nice, but what about the tap?
-
Alienbash over 3 yearsMaybe it's also worth noticing that
UIApplication
is part of UIKit, so one needs toimport UIKit
. -
ElKePoN over 3 yearsBest solution for swiftui 2.0, however, there is a crash when double clicking on text fields
-
ElKePoN over 3 years@Gary I get
AnyGestureRecognizer
can you share the code? -
Hasaan Ali over 3 yearsAwesome simple solution! I used this technique to hide keyboard whenever user taps anywhere outside the text field. See stackoverflow.com/a/65798558/1590911
-
Fero about 3 yearsDismissing 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 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 almost 3 yearsIt's actually easy to understand why it works
-
Roland Lariotte almost 3 yearsThis code disable other touch actions on the View.
-
Roland Lariotte almost 3 yearsThis code make
cancelsTouchesInView = true
. You can not tap on button that are present on the View. -
Roland Lariotte almost 3 yearsThis warning comes up when using this code
'windows' was deprecated in iOS 15.0: Use UIWindowScene.windows on a relevant window scene instead
. -
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 almost 3 years@pawello2222 Unfortunately, the iOS 15 solution do not allow you to tap outside the keyboard to dismiss it.
-
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 almost 3 years
eraseToAnyView
-
Galen Smith over 2 yearsI 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 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 over 2 yearsUse Color(UIColor.systemBackground) instead of Color.white
-
Redar over 2 yearsBy far, the best solution showing all necessary steps both for iOS 14 and iOS 15 👍
-
Jimbo over 2 yearsYou'll note that this adds a bar to all text fields. See here: stackoverflow.com/questions/69478735/…
-
Przemyslaw Jablonski over 2 years@JoeScotto This is amazing, thank you!
-
Galen Smith over 2 yearsSo in the iOS @Focused version, how would you dismiss the keyboard for a toggle or picker form field?
-
Liviu over 2 yearsI 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 over 2 yearsProper solution for +iOS15!
-
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 almost 2 yearsSimple and clean 👌but what if we use a phone pad keyboard 🤔
-
CD-3 almost 2 yearsExactly what I was looking for! Thank you. This should be the top answer now.
-
axlrtr almost 2 yearsThe 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 almost 2 yearsThis 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 almost 2 yearswhat if i have only one text field, should i create an enum for this text field ? sounds unreasonable.
-
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.