¿Cómo ocultar el teclado al usar SwiftUI?

88

¿Cómo ocultar el keyboarduso SwiftUIde los siguientes casos?

Caso 1

Tengo TextFieldy necesito ocultar el keyboardcuando el usuario hace clic en el returnbotón.

Caso 2

Tengo TextFieldy necesito ocultar el keyboardcuando 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.

Hitesh Surani
fuente
29
@DannyBuonocore ¡Lea mi pregunta con atención nuevamente!
Hitesh Surani
9
@DannyBuonocore Esto no es un duplicado de la pregunta mencionada. Esta pregunta es sobre SwiftUI, y otra es UIKit normal
Johnykutty
1
@DannyBuonocore, consulte developer.apple.com/documentation/swiftui para encontrar la diferencia entre UIKit y SwiftUI. Gracias
Hitesh Surani
Agregué mi solución aquí , espero que te ayude.
Victor Kushnerov
La mayoría de las soluciones aquí no funcionan como se desea, ya que desactivan las reacciones deseadas en otros grifos de control. Puede encontrar una solución de trabajo aquí: forums.developer.apple.com/thread/127196
Hardy

Respuestas:

79

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()
    }
}
rraphael
fuente
1
.keyWindowahora está en desuso. Vea la respuesta de Lorenzo Santini .
LinusGeffarth
3
Además, .tapActionha sido renombrado a.onTapGesture
LinusGeffarth
¿Se puede cerrar el teclado cuando se activa un control alternativo? stackoverflow.com/questions/58643512/…
Yarm
1
¿Hay alguna manera de hacer esto sin el fondo blanco? Estoy usando espaciadores y lo necesito para detectar un gesto de toque en el espaciador. Además, la estrategia de fondo blanco crea un problema en los iPhones más nuevos donde ahora hay espacio de pantalla adicional arriba. ¡Cualquier ayuda apreciada!
Joseph Astrahan
Publiqué una respuesta que mejora su diseño. Siéntase libre de editar su respuesta si lo desea. No me importa el crédito.
Joseph Astrahan
61

Después de muchos intentos, encontré una solución que (actualmente) no bloquea ningún control, agregando un reconocedor de gestos a UIWindow.

  1. Si desea cerrar el teclado solo en Tap afuera (sin manejar arrastres), entonces es suficiente usar solo UITapGestureRecognizery simplemente copiar el paso 3:
  2. 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
        }
    }
    
  3. En SceneDelegate.swiftel func 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)  
    
  4. Implementar UIGestureRecognizerDelegatepara 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

Mikhail
fuente
3
Esta respuesta debería estar en la parte superior. Otras respuestas fallan cuando hay otros controles en la vista.
Imthath
1
@RolandLariotte respuesta actualizada para corregir este comportamiento, mire la nueva implementación de AnyGestureRecognizer
Mikhail
1
Respuesta impresionante. Funciona perfectamente. @Mikhail está realmente interesado en saber cómo eliminar el reconocedor de gestos específicamente para algunos campos de texto (construí un autocompletar con etiquetas, así que cada vez que toco un elemento en la lista, no quiero que este campo de texto específico pierda el foco)
Pasta
1
esta solución es realmente excelente, pero después de usarla durante 3 meses, desafortunadamente encontré un error, causado directamente por este tipo de piratería. por favor, tenga en cuenta que lo mismo le sucede a usted
glassomoss
1
¡Respuesta fantástica! Me pregunto cómo se implementará esto con iOS 14 sin el scenedelegate.
Dom
28

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 correctamente keyWindow?.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)

Feldur
fuente
6
Hmmm: tocar otros controles ya no se registra. El evento se traga.
Yarm
No puedo replicar eso, todavía me está funcionando usando las últimas gotas de Apple a partir del 11/1. ¿Funcionó y luego dejó de funcionar para usted, o ??
Feldur
Si tiene un DatePicker en el formulario, entonces el DatePicker no se mostrará más
Albert
@Albert - eso es cierto; Para utilizar este enfoque, deberá desglosar dónde se decoran los elementos con DismissingKeyboard () a un nivel más detallado que se aplique a los elementos que deberían descartar y evitar el DatePicker.
Feldur
El uso de este código reproducirá la advertenciaCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314
23

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)})
DubluDe
fuente
Esto llevará a onDelete (deslizar para eliminar) a un comportamiento extraño.
Tarek Hallak
Esto está bien, pero ¿qué pasa con el grifo?
Danny182
20

Encontré otra forma de descartar el teclado que no requiere acceder a la keyWindowpropiedad; de hecho, el compilador devuelve una advertencia usando

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' quedó obsoleto en iOS 13.0: no debe usarse para aplicaciones que admitan múltiples escenas, ya que devuelve una ventana clave en todas las escenas conectadas

En su lugar, usé este código:

UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
Lorenzo Santini
fuente
15

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 ...

Dim Novo
fuente
4
Esto da otro problema: tengo un selector en el formulario {} junto al campo de texto, no responde. No encontré una solución usando todas las respuestas en este tema. Pero su respuesta es buena para descartar el teclado con un toque en otro lugar, si no usa selectores.
Nalov
Hola. mi código `` `var body: some View {NavigationView {Form {Section {TextField (" typesomething ", text: $ c)} Section {Picker (" name ", selection: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}}} `` `` El teclado se cierra cuando se toca en otro lugar, pero el selector no responde. No encontré la manera de hacerlo funcionar.
Nalov
2
Hola de nuevo, en este momento tengo dos soluciones: la primera, es usar el teclado nativo descartado en el botón de retorno, la segunda, es cambiar ligeramente el manejo del toque (también conocido como 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (count: 2, perform: {window.endEditing (true)})) Espero que esto te ayude ...
Dim Novo
Hola. Gracias. La segunda forma lo resolvió. Estoy usando el teclado numérico, por lo que los usuarios solo pueden ingresar números, no tiene tecla de retorno. Descartar con tapping era lo que estaba buscando.
Nalov
esto hará que no se pueda navegar por la lista.
Cui Mingda
13

SwiftUI 2

Aquí hay una solución actualizada para SwiftUI 2 / iOS 14 (originalmente propuesto aquí por Mikhail).

No usa AppDelegateni los SceneDelegateque 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)
    }
}
pawello2222
fuente
2
¡Funciona perfectamente! Gracias por la solución
NotAPhoenix
2
Esto debería estar en la parte superior porque tiene en cuenta el nuevo ciclo de vida de SwiftUI.
carlosobedgomez
Esto funciona muy bien. Sin embargo, si toco dos veces en un campo de texto, en lugar de seleccionar el texto, el teclado ahora desaparece. ¿Alguna idea de cómo puedo permitir el doble toque para la selección?
Gary
@Gary En la extensión inferior, puede ver la línea con el comentario establecido en falso si no desea detectar el toque durante otros gestos . Solo configúrelo en return false.
pawello2222
Configurarlo en falso funciona, pero el teclado tampoco descarta si alguien presiona o arrastra o se desplaza fuera del área de texto. ¿Hay alguna forma de establecerlo en falso solo para los dobles clics (preferiblemente los dobles clics dentro del campo de texto, pero incluso todos los dobles clics bastarían)?
Gary
11

Mi solución es cómo ocultar el teclado del software cuando los usuarios tocan afuera. Debe utilizar contentShapecon onLongPressGesturepara detectar todo el contenedor de vistas. onTapGesturenecesario para evitar bloquear el enfoque TextField. Puede usar en onTapGesturelugar de, onLongPressGesturepero 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: {})
    }
}
Víctor Kushnerov
fuente
Esto funcionó muy bien, lo usé de manera ligeramente diferente y tenía que estar seguro de que se llamaba en el hilo principal.
keegan3d
7

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)

        }
RyanTCB
fuente
7

Prefiero usar el .onLongPressGesture(minimumDuration: 0), que no hace que el teclado parpadee cuando TextViewse 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)
}
George Valkov
fuente
Todavía un parpadeo usando este método.
Daniel Ryan
Esto funcionó muy bien, lo usé de manera ligeramente diferente y tenía que estar seguro de que se llamaba en el hilo principal.
keegan3d
6

Porque keyWindowestá en desuso.

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}
msk
fuente
1
El forceparámetro no se utiliza. Debería ser{ $0.endEditing(force)}
Davide
5

Parece que la endEditingsolució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:

zero3nna
fuente
2
.keyWindowahora está en desuso. Vea la respuesta de Lorenzo Santini .
LinusGeffarth
Se deprecia en iOS 13+
Ahmadreza
4

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 por Gestures.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.

Sajjon
fuente
qué significa eraseToAny()
Ravindra_Bhati
2

¡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!

extensionHipotéticamente, podría aplicar esta técnica a cualquier control que necesite para admitir TapGestures que no lo haga actualmente y llamar a la onTapGesturefunción en combinación con self.endEditing()para cerrar el teclado en cualquier situación que desee.

José Astrahan
fuente
Mi pregunta ahora es ¿cómo se activa una confirmación en un campo de texto cuando el teclado desaparece de esta manera? actualmente, el 'compromiso' solo se activa si presiona la tecla de retorno en el teclado de iOS.
Joseph Astrahan
2

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

}
Michael Henry
fuente
Esto no funciona para una vista de formulario con campos de texto. La forma no aparece.
Nils
2

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))
    }
}
Nicolás Mandica
fuente
2

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.

josefdolezal
fuente
0

ReturnTecla del teclado

Ademá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 onCommitargumento:

TextField("title", text: $text, onCommit:  {
    resignFirstResponder()
})

Beneficios

  • Puedes llamarlo desde cualquier lugar
  • No depende de UIKit o SwiftUI (se puede usar en aplicaciones mac)
  • Funciona incluso en iOS 13

Manifestación

manifestación

Mojtaba Hosseini
fuente
0

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()
zdravko zdravkin
fuente
-2

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.

Marcel Mendes Filho
fuente
1
No hay modificador hideKeyboardOnTap en iOS14
Teo Sartori