How to hide keyboard when using SwiftUI?
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
.
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
.
- 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
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)
}
}