Como ocultar o teclado ao usar o SwiftUI?

88

Como esconder keyboardusando SwiftUIpara os casos abaixo?

Caso 1

Eu tenho TextFielde preciso ocultar o keyboardquando o usuário clica no returnbotão.

Caso 2

Eu tenho TextFielde preciso esconder o keyboardquando o usuário toca fora.

Como posso fazer isso usando SwiftUI?

Nota:

Eu não fiz uma pergunta a respeito UITextField. Eu quero fazer isso usando SwifUI.TextField.

Hitesh Surani
fonte
29
@DannyBuonocore Leia minha pergunta com atenção novamente!
Hitesh Surani
9
@DannyBuonocore Esta não é uma duplicata da pergunta mencionada. Esta pergunta é sobre SwiftUI e outra é UIKit normal
Johnykutty
1
@DannyBuonocore, consulte developer.apple.com/documentation/swiftui para encontrar a diferença entre UIKit e SwiftUI. Obrigado
Hitesh Surani
Eu adicionei minha solução aqui , espero que ajude você.
Victor Kushnerov
A maioria das soluções aqui não funciona como desejado, pois desabilitam as reações desejadas em outros toques de controle. Uma solução funcional pode ser encontrada aqui: forums.developer.apple.com/thread/127196
Hardy

Respostas:

79

Você pode forçar o primeiro respondente a renunciar enviando uma ação para o aplicativo compartilhado:

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

Agora você pode usar este método para fechar o teclado sempre que desejar:

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

Se você deseja fechar o teclado com um toque, pode criar uma visualização em tela inteira branca com uma ação de toque, que irá acionar 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
fonte
1
.keyWindowagora está obsoleto. Veja a resposta de Lorenzo Santini .
LinusGeffarth
3
Além disso, .tapActionfoi renomeado para.onTapGesture
LinusGeffarth
O teclado pode ser dispensado quando um controle alternativo se torna ativo? stackoverflow.com/questions/58643512/…
Yarm
1
Existe uma maneira de fazer isso sem o fundo branco? Estou usando espaçadores e preciso detectar um gesto de toque no espaçador. Além disso, a estratégia de fundo branco cria um problema nos iPhones mais recentes, onde agora há espaço extra na tela acima. Qualquer ajuda apreciada!
Joseph Astrahan
Publiquei uma resposta que melhora seu design. Sinta-se à vontade para fazer edições em sua resposta, se desejar. Não estou interessado em receber crédito.
Joseph Astrahan
61

Depois de muitas tentativas, encontrei uma solução que (atualmente) não bloqueia nenhum controle - adicionando o reconhecedor de gestos ao UIWindow.

  1. Se você deseja fechar o teclado apenas com um toque externo (sem manipular arrastos) - basta usar UITapGestureRecognizere apenas copiar a etapa 3:
  2. Crie uma classe de reconhecedor de gestos personalizada que funciona com qualquer 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. No SceneDelegate.swiftno func scene, adicione seguinte 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. Implemente UIGestureRecognizerDelegatepara permitir toques simultâneos.

    extension SceneDelegate: UIGestureRecognizerDelegate {
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            return true
        }
    }
    

Agora, qualquer teclado em qualquer visualização será fechado ao tocar ou arrastar para fora.

PS Se você deseja fechar apenas TextFields específicos - então adicione e remova o reconhecedor de gestos da janela sempre que for chamado de retorno de TextField onEditingChanged

Mikhail
fonte
3
Essa resposta deve estar no topo. Outras respostas falham quando há outros controles na visualização.
Imthath
1
@RolandLariotte atualizou a resposta para corrigir esse comportamento, veja a nova implementação de AnyGestureRecognizer
Mikhail
1
Resposta incrível. Funciona perfeitamente. @Mikhail está realmente interessado em saber como você remove o reconhecedor de gestos especificamente para alguns campos de texto (eu construí um preenchimento automático com tags, então toda vez que toco em um elemento da lista, não quero que esse campo de texto específico perca o foco)
Pasta
1
esta solução é realmente ótima, mas depois de usá-la por cerca de 3 meses, infelizmente encontrei um bug, diretamente causado por esse tipo de hack. por favor, esteja ciente do mesmo acontecendo com você
glassomoss
1
Resposta fantástica! Eu me pergunto como isso será implementado com o iOS 14 sem o scenedelegate?
Dom
28

A resposta de @RyanTCB é boa; Aqui estão alguns refinamentos que o tornam mais simples de usar e evitam uma possível falha:

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

A 'correção de bug' é simplesmente que keyWindow!.endEditing(true)deveria ser keyWindow?.endEditing(true)(sim, você pode argumentar que isso não pode acontecer).

Mais interessante é como você pode usá-lo. Por exemplo, suponha que você tenha um formulário com vários campos editáveis. Basta embrulhar assim:

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

Agora, tocar em qualquer controle que não apresente um teclado fará o descarte apropriado.

(Testado com beta 7)

Feldur
fonte
6
Hmmm - tocar em outros controles não registra mais. O evento é engolido.
Yarm
Não consigo replicar isso - ainda está funcionando para mim usando os últimos lançamentos da Apple em 01/11. Funcionou e depois parou de funcionar para você, ou ??
Feldur 01 de
Se você tiver um DatePicker no formulário, o DatePicker não será mais mostrado
Albert
@Albert - isso é verdade; para usar essa abordagem, você terá que dividir onde os itens são decorados com DismissingKeyboard () em um nível mais refinado que se aplica aos elementos que devem dispensar e evita o DatePicker.
Feldur
O uso deste código reproduzirá o avisoCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314
23

Eu experimentei isso ao usar um TextField dentro de um NavigationView. Esta é a minha solução para isso. Isso dispensará o teclado quando você começar a rolar.

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
fonte
Isso levará onDelete (deslize para excluir) a um comportamento estranho.
Tarek Hallak
Isso é bom, mas e a torneira?
Danny182
20

Encontrei outra maneira de dispensar o teclado que não requer acesso à keyWindowpropriedade; na verdade, o compilador dá um aviso usando

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' foi preterido no iOS 13.0: não deve ser usado para aplicativos que oferecem suporte a várias cenas, pois retorna uma janela principal em todas as cenas conectadas

Em vez disso, usei este código:

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

SwiftUI no arquivo 'SceneDelegate.swift' basta adicionar: .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()
        }
    }

isso é o suficiente para cada visualização usando o teclado em seu aplicativo ...

Dim Novo
fonte
4
Isso causa outro problema - eu tenho um seletor no Form {} ao lado do campo de texto, ele parou de responder. Não encontrei uma solução para isso usando todas as respostas neste tópico. Mas sua resposta é boa para descartar o teclado com um toque em outro lugar - se você não usar seletores.
Nalov
Olá. 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])%")}}} `` `O teclado é dispensado ao tocar em outro lugar, mas o seletor deixou de responder. Não encontrei uma maneira de fazer funcionar.
Nalov
2
Oi de novo, no momento eu tenho duas soluções: a primeira - é usar o teclado nativo dispensado no botão de retorno, a segunda - é mudar um pouco o tratamento de toque (também conhecido como 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (contagem: 2, executar: {window.endEditing (true)})) Espero que isso ajude você ...
Dim Novo
Olá. Obrigado. A segunda maneira resolveu. Estou usando o teclado numérico, então os usuários podem inserir apenas números, não tem tecla de retorno. Dispensar com tapping era o que eu procurava.
Nalov
isso fará com que a lista não possa ser navegada.
Cui Mingda
13

SwiftUI 2

Aqui está uma solução atualizada para SwiftUI 2 / iOS 14 (originalmente proposta aqui por Mikhail).

Ele não usa AppDelegatenem o SceneDelegateque estão faltando se você usar o 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
    }
}

Aqui está um exemplo de como detectar gestos simultâneos, exceto gestos Long Press:

extension UIApplication: UIGestureRecognizerDelegate {
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
    }
}
pawello2222
fonte
2
Isso funciona perfeitamente! Obrigado pela solução
NotAPhoenix
2
Isso deve estar no topo porque lembre-se do novo ciclo de vida do SwiftUI.
carlosobedgomez
Isso funciona muito bem. No entanto, se eu tocar duas vezes em um campo de texto, em vez de selecionar o texto, o teclado desaparecerá. Alguma ideia de como posso permitir o toque duplo para seleção?
Gary
@Gary Na extensão inferior, você pode ver a linha com o comentário definido como falso se não quiser detectar o toque durante outros gestos . Basta definir como return false.
pawello2222 01 de
Definir como falso funciona, mas o teclado também não descarta se alguém pressiona ou arrasta ou rola para fora da área de texto. Existe alguma maneira de defini-lo como falso apenas para cliques duplos (de preferência, cliques duplos dentro do campo de texto, mas mesmo apenas todos os cliques duplos serviriam).
Gary
11

Minha solução como esconder o teclado do software quando os usuários tocam fora. Você precisa usar contentShapecom onLongPressGesturepara detectar todo o container View. onTapGesturenecessário para evitar o bloqueio do foco TextField. Você pode usar em onTapGesturevez de, onLongPressGesturemas os itens de NavigationBar não funcionarão.

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: {})
    }
}
Victor Kushnerov
fonte
Funcionou muito bem, usei-o de maneira um pouco diferente e precisava ter certeza de que era chamado no thread principal.
keegan3d
7

adicione este modificador à vista que deseja detectar toques do usuário

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

Prefiro usar o .onLongPressGesture(minimumDuration: 0), o que não faz com que o teclado pisque quando outro TextViewé ativado (efeito colateral de .onTapGesture). O código de teclado oculto pode ser uma função reutilizável.

.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
fonte
Ainda está piscando usando este método.
Daniel Ryan
Funcionou muito bem, usei-o de maneira um pouco diferente e precisava ter certeza de que era chamado no thread principal.
keegan3d
6

Porque keyWindowestá obsoleto.

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}
msk
fonte
1
O forceparâmetro não é usado. Deveria ser{ $0.endEditing(force)}
Davide
5

Parece que a endEditingsolução é a única como @rraphael apontou.
O exemplo mais limpo que vi até agora é este:

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.keyWindow?.endEditing(force)
    }
}

e então usá-lo no onCommit:

zero3nna
fonte
2
.keyWindowagora está obsoleto. Veja a resposta de Lorenzo Santini .
LinusGeffarth
É depreciado no iOS 13+
Ahmadreza
4

Expandindo a resposta de @Feldur (que foi baseada em @ RyanTCB), aqui está uma solução ainda mais expressiva e poderosa que permite dispensar o teclado em outros gestos do que onTapGesturevocê pode especificar qual deseja na chamada de função.

Uso

// MARK: - View
extension RestoreAccountInputMnemonicScreen: View {
    var body: some View {
        List(viewModel.inputWords) { inputMnemonicWord in
            InputMnemonicCell(mnemonicInput: inputMnemonicWord)
        }
        .dismissKeyboard(on: [.tap, .drag])
    }
}

Ou usando All.gestures(apenas açúcar para 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))
    }
}

Palavra de cautela

Por favor, note que se você usar todos os gestos, eles podem entrar em conflito e eu não encontrei nenhuma solução legal para isso.

Sajjon
fonte
o que significa eraseToAny()
Ravindra_Bhati
2

Este método permite ocultar o teclado em espaçadores!

Primeiro adicione esta função (Crédito dado a: Casper Zandbergen, do SwiftUI não pode tocar no espaçador do 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
        }
    }
}

Em seguida, adicione as 2 funções a seguir (Crédito dado a: rraphael, desta questão)

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

A função abaixo seria adicionada à sua classe View, apenas consulte a principal resposta aqui de rraphael para mais detalhes.

private func endEditing() {
   UIApplication.shared.endEditing()
}

Finalmente, você pode simplesmente ligar para ...

Spacer().onTapGesture {
    self.endEditing()
}

Isso fará com que qualquer área do espaçador feche o teclado agora. Não há mais necessidade de uma grande visão de fundo branco!

Você poderia, hipoteticamente, aplicar esta técnica extensiona quaisquer controles de que precise para suportar TapGestures que atualmente não o fazem e chamar a onTapGesturefunção em combinação com self.endEditing()para fechar o teclado em qualquer situação desejada.

Joseph Astrahan
fonte
Minha pergunta agora é como você dispara um commit em um campo de texto quando você faz com que o teclado desapareça dessa forma? atualmente, o 'commit' só dispara se você pressionar a tecla Enter no teclado do iOS.
Joseph Astrahan
2

Verifique https://github.com/michaelhenry/KeyboardAvoider

Basta incluir KeyboardAvoider {}no topo de sua visualização principal e pronto.

KeyboardAvoider {
    VStack { 
        TextField()
        TextField()
        TextField()
        TextField()
    }

}
Michael Henry
fonte
Isso não funciona para uma visualização de formulário com campos de texto. O formulário não aparece.
Nils
2

Com base na resposta de @Sajjon, aqui está uma solução que permite dispensar o teclado com um toque, manter pressionado, arrastar, ampliar e gestos de rotação de acordo com sua escolha.

Esta solução está funcionando no XCode 11.4

Uso para obter o comportamento 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))
    }
}
Nicolas Mandica
fonte
2

Você pode evitar completamente a interação com o UIKit e implementá-lo em SwiftUI puro . Basta adicionar um .id(<your id>)modificador ao seu TextFielde alterar seu valor sempre que desejar dispensar o teclado (ao deslizar, ver toque, ação do botão, ..).

Implementação de amostra:

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

Observe que eu apenas testei no último Xcode 12 beta, mas ele deve funcionar com versões mais antigas (até mesmo o Xcode 11) sem nenhum problema.

Josefdolezal
fonte
0

ReturnTecla do teclado

Além de todas as respostas sobre como tocar fora de textField, você pode dispensar o teclado quando o usuário tocar na tecla Return do teclado:

definir esta função global:

func resignFirstResponder() {
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}

E adicione o uso no onCommitargumento:

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

Benefícios

  • Você pode ligar de qualquer lugar
  • Não depende de UIKit ou SwiftUI (pode ser usado em aplicativos mac)
  • Funciona até no iOS 13

Demo

demo

Mojtaba Hosseini
fonte
0

Até agora, as opções acima não funcionaram para mim, porque tenho Formulários e botões internos, links, seletor ...

Eu crio o código abaixo que está funcionando, com a ajuda dos exemplos acima.

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
fonte
-2

SwiftUI lançado em junho / 2020 com Xcode 12 e iOS 14 adiciona o modificador hideKeyboardOnTap (). Isso deve resolver seu caso número 2. A solução para seu caso número 1 vem de graça com o Xcode 12 e iOS 14: o teclado padrão para TextField oculta automaticamente quando o botão Return é pressionado.

Marcel Mendes Filho
fonte
1
Não há modificador hideKeyboardOnTap no iOS14
Teo Sartori