¿Cómo ocultar el keyboard
uso SwiftUI
de los siguientes casos?
Caso 1
Tengo TextField
y necesito ocultar el keyboard
cuando el usuario hace clic en el return
botón.
Caso 2
Tengo TextField
y necesito ocultar el keyboard
cuando el usuario toca afuera.
¿Cómo puedo hacer esto usando SwiftUI
?
Nota:
No he hecho una pregunta al respecto UITextField
. Quiero hacerlo usando SwifUI.TextField
.
Respuestas:
Puede obligar al primer respondedor a renunciar enviando una acción a la aplicación compartida:
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Ahora puede usar este método para cerrar el teclado cuando lo desee:
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() } } } }
Si desea cerrar el teclado con un toque, puede crear una vista en blanco de pantalla completa con una acción de toque, que activará
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() } }
fuente
.keyWindow
ahora está en desuso. Vea la respuesta de Lorenzo Santini ..tapAction
ha sido renombrado a.onTapGesture
Después de muchos intentos, encontré una solución que (actualmente) no bloquea ningún control, agregando un reconocedor de gestos a
UIWindow
.UITapGestureRecognizer
y simplemente copiar el paso 3:Cree una clase de reconocimiento de gestos personalizada que funcione con cualquier toque:
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 } }
En
SceneDelegate.swift
elfunc scene
, agregue el siguiente código: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)
Implementar
UIGestureRecognizerDelegate
para permitir toques simultáneos.extension SceneDelegate: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
Ahora cualquier teclado en cualquier vista se cerrará al tocarlo o arrastrarlo hacia afuera.
PD: si desea cerrar solo TextFields específicos, agregue y elimine el reconocedor de gestos a la ventana cada vez que se llame a la devolución de llamada de TextField
onEditingChanged
fuente
La respuesta de @ RyanTCB es buena; Aquí hay un par de mejoras que facilitan su uso y evitan un posible bloqueo:
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) } } }
La 'corrección de errores' es simplemente que
keyWindow!.endEditing(true)
debería ser correctamentekeyWindow?.endEditing(true)
(sí, podría argumentar que no puede suceder).Más interesante es cómo se puede utilizar. Por ejemplo, suponga que tiene un formulario con varios campos editables. Simplemente envuélvalo así:
Form { . . . } .modifier(DismissingKeyboard())
Ahora, al tocar cualquier control que en sí mismo no presente un teclado, se descartará adecuadamente.
(Probado con beta 7)
fuente
Can't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
Experimenté esto mientras usaba un TextField dentro de un NavigationView. Esta es mi solución para eso. Descartará el teclado cuando comience a desplazarse.
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)})
fuente
Encontré otra forma de descartar el teclado que no requiere acceder a la
keyWindow
propiedad; de hecho, el compilador devuelve una advertencia usandoUIApplication.shared.keyWindow?.endEditing(true)
En su lugar, usé este código:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
fuente
SwiftUI en el archivo 'SceneDelegate.swift' simplemente agregue: .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() } }
esto es suficiente para cada Vista usando el teclado en su aplicación ...
fuente
SwiftUI 2
Aquí hay una solución actualizada para SwiftUI 2 / iOS 14 (originalmente propuesto aquí por Mikhail).
No usa
AppDelegate
ni losSceneDelegate
que faltan si usa el ciclo de vida SwiftUI:@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 } }
A continuación, se muestra un ejemplo de cómo detectar gestos simultáneos, excepto los gestos de pulsación larga:
extension UIApplication: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) } }
fuente
return false
.Mi solución es cómo ocultar el teclado del software cuando los usuarios tocan afuera. Debe utilizar
contentShape
cononLongPressGesture
para detectar todo el contenedor de vistas.onTapGesture
necesario para evitar bloquear el enfoqueTextField
. Puede usar enonTapGesture
lugar de,onLongPressGesture
pero los elementos de NavigationBar no funcionarán.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: {}) } }
fuente
agregue este modificador a la vista en la que desea detectar los toques del usuario
.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) }
fuente
Prefiero usar el
.onLongPressGesture(minimumDuration: 0)
, que no hace que el teclado parpadee cuandoTextView
se activa otro (efecto secundario de.onTapGesture
). Ocultar el código del teclado puede ser una función reutilizable..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) }
fuente
Porque
keyWindow
está en desuso.extension View { func endEditing(_ force: Bool) { UIApplication.shared.windows.forEach { $0.endEditing(force)} } }
fuente
force
parámetro no se utiliza. Debería ser{ $0.endEditing(force)}
Parece que la
endEditing
solución es la única como señaló @rraphael.El ejemplo más limpio que he visto hasta ahora es este:
extension View { func endEditing(_ force: Bool) { UIApplication.shared.keyWindow?.endEditing(force) } }
y luego usarlo en el
onCommit:
fuente
.keyWindow
ahora está en desuso. Vea la respuesta de Lorenzo Santini .Ampliando la respuesta de @Feldur (que se basó en @ RyanTCB), aquí hay una solución aún más expresiva y poderosa que le permite descartar el teclado en otros gestos que
onTapGesture
, puede especificar cuál desea en la llamada de función.Uso
// MARK: - View extension RestoreAccountInputMnemonicScreen: View { var body: some View { List(viewModel.inputWords) { inputMnemonicWord in InputMnemonicCell(mnemonicInput: inputMnemonicWord) } .dismissKeyboard(on: [.tap, .drag]) } }
O usando
All.gestures
(solo azúcar porGestures.allCases
🍬).dismissKeyboard(on: All.gestures)
Código
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)) } }
Advertencia
Tenga en cuenta que si usa todos los gestos, podrían entrar en conflicto y no se me ocurrió ninguna solución ordenada para resolver eso.
fuente
eraseToAny()
¡Este método le permite ocultar el teclado en espaciadores!
Primero agregue esta función (crédito otorgado a: Casper Zandbergen, de SwiftUI no puedo tocar en el espaciador de 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 } } }
A continuación, agregue las siguientes 2 funciones (Crédito dado a: rraphael, de esta pregunta)
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
La función a continuación se agregaría a su clase View, solo consulte la respuesta principal aquí de rraphael para obtener más detalles.
private func endEditing() { UIApplication.shared.endEditing() }
Finalmente, ahora puede simplemente llamar ...
Spacer().onTapGesture { self.endEditing() }
Esto hará que cualquier área espaciadora cierre el teclado ahora. ¡Ya no es necesario tener una gran vista de fondo blanco!
extension
Hipotéticamente, podría aplicar esta técnica a cualquier control que necesite para admitir TapGestures que no lo haga actualmente y llamar a laonTapGesture
función en combinación conself.endEditing()
para cerrar el teclado en cualquier situación que desee.fuente
Consulte https://github.com/michaelhenry/KeyboardAvoider
Solo incluye
KeyboardAvoider {}
en la parte superior de tu vista principal y eso es todo.KeyboardAvoider { VStack { TextField() TextField() TextField() TextField() } }
fuente
Según la respuesta de @ Sajjon, aquí hay una solución que le permite descartar el teclado al tocar, presionar prolongadamente, arrastrar, ampliar y girar los gestos de acuerdo con su elección.
Esta solución funciona en XCode 11.4
Uso para obtener el comportamiento solicitado por @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) } }
Código
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)) } }
fuente
Puede evitar completamente la interacción con UIKit e implementarlo en SwiftUI puro . Simplemente agregue un
.id(<your id>)
modificador a suTextField
y cambie su valor cada vez que desee descartar el teclado (al deslizar, ver toque, acción del botón, ..).Implementación de muestra:
struct MyView: View { @State private var text: String = "" @State private var textFieldId: String = UUID().uuidString var body: some View { VStack { TextField("Type here", text: $text) .id(textFieldId) Spacer() Button("Dismiss", action: { textFieldId = UUID().uuidString }) } } }
Tenga en cuenta que solo lo probé en la última versión beta de Xcode 12, pero debería funcionar con versiones anteriores (incluso Xcode 11) sin ningún problema.
fuente
Return
Tecla del tecladoAdemás de todas las respuestas sobre tocar fuera del campo de texto, es posible que desee cerrar el teclado cuando el usuario toque la tecla de retorno en el teclado:
definir esta función global:
func resignFirstResponder() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) }
Y agregue uso en el
onCommit
argumento:TextField("title", text: $text, onCommit: { resignFirstResponder() })
Beneficios
Manifestación
fuente
Hasta ahora, las opciones anteriores no me funcionaron, porque tengo Form y botones internos, enlaces, selector ...
Creo el siguiente código que funciona, con la ayuda de los ejemplos anteriores.
import Combine import SwiftUI private class KeyboardListener: ObservableObject { @Published var keyabordIsShowing: Bool = false var cancellable = Set<AnyCancellable>() init() { NotificationCenter.default .publisher(for: UIResponder.keyboardWillShowNotification) .sink { [weak self ] _ in self?.keyabordIsShowing = true } .store(in: &cancellable) NotificationCenter.default .publisher(for: UIResponder.keyboardWillHideNotification) .sink { [weak self ] _ in self?.keyabordIsShowing = false } .store(in: &cancellable) } } private struct DismissingKeyboard: ViewModifier { @ObservedObject var keyboardListener = KeyboardListener() fileprivate func body(content: Content) -> some View { ZStack { content Rectangle() .background(Color.clear) .opacity(keyboardListener.keyabordIsShowing ? 0.01 : 0) .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) .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) } } } } extension View { func dismissingKeyboard() -> some View { ModifiedContent(content: self, modifier: DismissingKeyboard()) } }
Uso:
var body: some View { NavigationView { Form { picker button textfield text } .dismissingKeyboard()
fuente
SwiftUI lanzado en junio de 2020 con Xcode 12 e iOS 14 agrega el modificador hideKeyboardOnTap (). Esto debería resolver su caso número 2. La solución para su caso número 1 viene gratis con Xcode 12 y iOS 14: el teclado predeterminado para TextField se oculta automáticamente cuando se presiona el botón Retorno.
fuente