117 votes

SwiftUI : Comment faire en sorte que TextField devienne le premier intervenant ?

Voici mon SwiftUI code :

struct ContentView : View {

    @State var showingTextField = false
    @State var text = ""

    var body: some View {
        return VStack {
            if showingTextField {
                TextField($text)
            }
            Button(action: { self.showingTextField.toggle() }) {
                Text ("Show")
            }
        }
    }
}

Ce que je veux, c'est que lorsque le champ de texte devient visible pour que le champ de texte devienne le premier intervenant (c'est-à-dire qu'il reçoive le focus et que le clavier s'affiche).

14 votes

Le fait que quelque chose de basique comme cela ne soit même pas supporté montre à quel point SwiftUI est immature pour le moment. Regardez les solutions ci-dessous, c'est ridicule pour quelque chose qui est censé rendre le codage de l'interface graphique "plus simple".

0voto

Nadia Siddiqah Points 23

En complément de la réponse de @JoshuaKifer ci-dessus, si vous rencontrez des problèmes avec l'animation de navigation lors de l'utilisation d'Introspect pour créer un champ de texte pour les premiers répondants. Utilisez ceci :

import SchafKit

@State var field: UITextField?

TextField("", text: $value)
.introspectTextField { textField in
    field = textField
}
.onDidAppear {
     field?.becomeFirstResponder()
}

Plus de détails sur cette solution aquí .

0voto

alex1704 Points 1

J'ai pris une approche un peu différente - au lieu de UIViewRepresentable sur la base de UITextField Je l'ai fait en me basant sur UIView et de l'insérer dans la hiérarchie des vues de SwiftUI avec background modificateur. À l'intérieur de UIView J'ai ajouté une logique pour trouver la première vue qui canBecomeFirstResponder dans les sous-vues et les vues parent.

private struct FocusableUIView: UIViewRepresentable {
    var isFirstResponder: Bool = false

    class Coordinator: NSObject {
        var didBecomeFirstResponder = false
    }

    func makeUIView(context: UIViewRepresentableContext<FocusableUIView>) -> UIView {
        let view = UIView()
        view.backgroundColor = .clear
        return view
    }

    func makeCoordinator() -> FocusableUIView.Coordinator {
        return Coordinator()
    }

    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<FocusableUIView>) {
        guard uiView.window != nil, isFirstResponder, !context.coordinator.didBecomeFirstResponder else {
            return
        }

        var foundRepsonder: UIView?
        var currentSuperview: UIView? = uiView
        repeat {
            foundRepsonder = currentSuperview?.subviewFirstPossibleResponder
            currentSuperview = currentSuperview?.superview
        } while foundRepsonder == nil && currentSuperview != nil

        guard let responder = foundRepsonder else {
            return
        }

        DispatchQueue.main.async {
            responder.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }
}

private extension UIView {
    var subviewFirstPossibleResponder: UIView? {
        guard !canBecomeFirstResponder else { return self }

        for subview in subviews {
            if let firstResponder = subview.subviewFirstPossibleResponder {
                return firstResponder
            }
        }

        return nil
    }
}

Voici un exemple d'utilisation de l'autofocus d'un champ de texte (+ bonus). @FocusState nouvelle api iOS 15).

extension View {
    @ViewBuilder
    func autofocus() -> some View {
        if #available(iOS 15, *) {
            modifier(AutofocusedViewModifiers.Modern())
        } else {
            modifier(AutofocusedViewModifiers.Legacy())
        }
    }
}

private enum AutofocusedViewModifiers {
    struct Legacy: ViewModifier {
        func body(content: Content) -> some View {
            content
                .background(FocusableUIView(isFirstResponder: isFocused))
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        isFocused = true
                    }
                }
        }

        @State private var isFocused = false
    }

    @available(iOS 15, *)
    struct Modern: ViewModifier {
        func body(content: Content) -> some View {
            content
                .focused($isFocused)
                .onAppear {
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                        isFocused = true
                    }
                }
        }

        @FocusState private var isFocused: Bool
    }
}

Exemple de vue du contenu :

struct ContentView: View {
    @State private var text = ""

    var body: some View {
        VStack {
            TextField("placeholder", text: $text)
            Text("some text")
        }
        .autofocus()
    }
}

0voto

Markon Points 283

La méthode correcte de SwiftUI est d'utiliser @FocusState comme indiqué ci-dessus. Cependant, cette API n'est disponible que pour iOS 15. Si vous utilisez iOS 14 ou iOS 13, vous pouvez utiliser la bibliothèque Focuser qui est modelée pour suivre l'API d'Apple.

https://github.com/art-technologies/swift-focuser

enter image description here

Voici un exemple de code. Vous remarquerez que l'API ressemble presque exactement à celle d'Apple, mais Focuser propose également d'utiliser le clavier pour faire descendre le premier intervenant dans la chaîne, ce qui est plutôt pratique.

enter image description here

0voto

Gabriel D M Points 1

Si vous avez un problème avec @JoshuaKifer o @ahaze La réponse de l'entreprise, J'ai résolu la mienne en en utilisant le modificateur sur la classe mère plutôt que sur le champ de texte lui-même.

Ce que je faisais :

TextField("Placeholder text...", text: $searchInput)
    .introspectTextField { textField in
        textField.becomeFirstResponder()
    }

Comment j'ai résolu mon problème :

   YourParentStruct(searchInput: $searchInput)
       .introspectTextField { textField in
           textField.becomeFirstResponder()
       }

Je vais mettre la définition de la structure parentale ci-dessous pour plus de clarté.

   struct YourParentStruct: View {
       @Binding var searchInput: String

       var body: some View {
           HStack {
               TextField("Placeholder text...", text: $searchInput)                      
                  .padding()
                  .background(Color.gray)
                  .cornerRadius(10)
           }
       }
   }

-3voto

vauxhall Points 648

Comme SwiftUI 2 ne prend pas en charge les premiers intervenants, pourtant j'utilise cette solution. Elle est sale, mais peut fonctionner pour certains cas d'utilisation lorsque vous n'avez qu'une seule personne. UITextField et 1 UIWindow .

import SwiftUI

struct MyView: View {
    @Binding var text: String

    var body: some View {
        TextField("Hello world", text: $text)
            .onAppear {
                UIApplication.shared.windows.first?.rootViewController?.view.textField?.becomeFirstResponder()
            }
    }
}

private extension UIView {
    var textField: UITextField? {
        subviews.compactMap { $0 as? UITextField }.first ??
            subviews.compactMap { $0.textField }.first
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X