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 quand le champ de texte devient visible, pour que le champ de texte devienne le premier répondant (c'est-à-dire recevoir le focus et faire apparaître le clavier).
Cela ne semble pas possible pour le moment, mais vous pouvez implémenter vous-même quelque chose de similaire.
Vous pouvez créer un champ de texte personnalisé et ajouter une valeur pour qu'il devienne le premier répondant.
struct CustomTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
var didBecomeFirstResponder = false
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
@Binding var text: String
var isFirstResponder: Bool = false
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> CustomTextField.Coordinator {
return Coordinator(text: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = text
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder = true
}
}
}
Remarque: didBecomeFirstResponder
est nécessaire pour s'assurer que le champ de texte ne devient le premier répondant qu'une seule fois, pas à chaque rafraîchissement par SwiftUI
!
Vous l'utiliseriez comme ça ...
struct ContentView : View {
@State var text: String = ""
var body: some View {
CustomTextField(text: $text, isFirstResponder: true)
.frame(width: 300, height: 50)
.background(Color.red)
}
}
P.S. J'ai ajouté un frame
car il ne se comporte pas comme le stock TextField
, ce qui signifie qu'il se passe plus de choses en coulisses.
En savoir plus sur Coordinators
dans cet excellent exposé sur la WWDC 19: Integrating SwiftUI
Testé sur Xcode 11.1
En utilisant https://github.com/timbersoftware/SwiftUI-Introspect on peut:
TextField("", text: $value)
.introspectTextField { textField in
textField.becomeFirstResponder()
}
Pour tous ceux qui se sont retrouvés ici mais qui se sont écrasés en utilisant la réponse de @Matteo Pacini, veuillez être conscient de ce changement dans la version bêta 4: Impossible d'assigner à la propriété: '$ text' est immuable à propos de ce bloc:
init(text: Binding<String>) {
$text = text
}
devrait utiliser:
init(text: Binding<String>) {
_text = text
}
Et si vous voulez que le champ de texte devienne le premier répondant dans un sheet
, sachez que vous ne pouvez pas appeler becomeFirstResponder
tant que le champ de texte n'est pas affiché. En d'autres termes, placer le champ de texte de @Matteo Pacini directement dans le contenu sheet
provoque un crash.
Pour résoudre le problème, ajoutez une vérification supplémentaire uiView.window != nil
pour la visibilité de textfield. Ne vous concentrez qu'une fois dans la hiérarchie des vues:
struct AutoFocusTextField: UIViewRepresentable {
@Binding var text: String
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<AutoFocusTextField>) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiView: UITextField, context:
UIViewRepresentableContext<AutoFocusTextField>) {
uiView.text = text
if uiView.window != nil, !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: AutoFocusTextField
init(_ autoFocusTextField: AutoFocusTextField) {
self.parent = autoFocusTextField
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
}
struct ResponderTextField: UIViewRepresentable {
typealias TheUIView = UITextField
var isFirstResponder: Bool
var configuration = { (view: TheUIView) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> TheUIView { TheUIView() }
func updateUIView(_ uiView: TheUIView, context: UIViewRepresentableContext<Self>) {
_ = isFirstResponder ? uiView.becomeFirstResponder() : uiView.resignFirstResponder()
configuration(uiView)
}
}
struct ContentView: View {
@State private var isActive = false
var body: some View {
ResponderTextField(isFirstResponder: isActive)
// Just set `isActive` anyway you like
}
}
ResponderTextField(isFirstResponder: isActive) {
$0.textColor = .red
$0.tintColor = .blue
}
Cette méthode est entièrement adaptable. Par exemple, vous pouvez voir Comment ajouter un indicateur d'activité dans SwiftUI avec la même méthode ici
Comme d'autres l'ont noté (par exemple @ kipelovets commentent la réponse acceptée , par exemple @ réponse d'Eonil ), je n'ai également trouvé aucune des solutions acceptées pour travailler sur macOS. J'ai eu de la chance, cependant, d'utiliser NSViewControllerRepresentable pour obtenir un NSSearchField à apparaître comme premier répondeur dans une vue SwiftUI:
import Cocoa
import SwiftUI
class FirstResponderNSSearchFieldController: NSViewController {
@Binding var text: String
init(text: Binding<String>) {
self._text = text
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
let searchField = NSSearchField()
searchField.delegate = self
self.view = searchField
}
override func viewDidAppear() {
self.view.window?.makeFirstResponder(self.view)
}
}
extension FirstResponderNSSearchFieldController: NSSearchFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
if let textField = obj.object as? NSTextField {
self.text = textField.stringValue
}
}
}
struct FirstResponderNSSearchFieldRepresentable: NSViewControllerRepresentable {
@Binding var text: String
func makeNSViewController(
context: NSViewControllerRepresentableContext<FirstResponderNSSearchFieldRepresentable>
) -> FirstResponderNSSearchFieldController {
return FirstResponderNSSearchFieldController(text: $text)
}
func updateNSViewController(
_ nsViewController: FirstResponderNSSearchFieldController,
context: NSViewControllerRepresentableContext<FirstResponderNSSearchFieldRepresentable>
) {
}
}
Exemple de vue SwiftUI:
struct ContentView: View {
@State private var text: String = ""
var body: some View {
FirstResponderNSSearchFieldRepresentable(text: $text)
}
}
La réponse sélectionnée provoque un problème de boucle infinie avec AppKit. Je ne connais pas l'affaire UIKit.
Pour éviter ce problème, je recommande de partager directement l'instance NSTextField
directement.
import AppKit
import SwiftUI
struct Sample1: NSViewRepresentable {
var textField: NSTextField
func makeNSView(context:NSViewRepresentableContext<Sample1>) -> NSView { textField }
func updateNSView(_ x:NSView, context:NSViewRepresentableContext<Sample1>) {}
}
Vous pouvez l'utiliser comme ça.
let win = NSWindow()
let txt = NSTextField()
win.setIsVisible(true)
win.setContentSize(NSSize(width: 256, height: 256))
win.center()
win.contentView = NSHostingView(rootView: Sample1(textField: txt))
win.makeFirstResponder(txt)
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.run()
Cela rompt la sémantique de la valeur pure, mais en fonction d'AppKit, vous abandonnez partiellement la sémantique de la valeur pure et allez vous offrir un peu de saleté. C'est un trou magique dont nous avons besoin en ce moment pour faire face au manque de contrôle des premiers intervenants dans SwiftUI.
Comme nous accédons directement à NSTextField
, la définition du premier répondeur est une manière simple d'AppKit, donc aucune source visible de problème.
Vous pouvez télécharger le code source de travail ici .