Es scheint im Moment nicht möglich zu sein, aber Sie können etwas Ähnliches selbst implementieren.
Sie können ein benutzerdefiniertes Textfeld erstellen und einen Wert hinzufügen, damit es zum Ersthelfer wird.
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
}
}
}
Hinweis: didBecomeFirstResponder
Wird benötigt, um sicherzustellen, dass das Textfeld nur einmal zum Ersthelfer wird, nicht bei jeder Aktualisierung durch SwiftUI
!
Sie würden es so verwenden ...
struct ContentView : View {
@State var text: String = ""
var body: some View {
CustomTextField(text: $text, isFirstResponder: true)
.frame(width: 300, height: 50)
.background(Color.red)
}
}
PS Ich habe ein hinzugefügt, frame
da es sich nicht wie die Aktie verhält TextField
, was bedeutet, dass hinter den Kulissen mehr los ist.
Mehr dazu Coordinators
in diesem ausgezeichneten WWDC 19-Vortrag:
Integration von SwiftUI
Getestet auf Xcode 11.4
textfield.becomeFirstResponder()
innerhalb dazu führte,override func layoutSubviews
dass meine App in eine Endlosschleife eintrat. Dies geschah, als ich versuchte, einen neuen ViewController zu verwenden, der auch eine Textansicht hatte, die versuchte, in layoutSubviews zum ersten Antwortgeber zu werden. Auf jeden Fall ein hinterhältiger Fehler, den man beachten sollte!Mit https://github.com/timbersoftware/SwiftUI-Introspect kann man:
TextField("", text: $value) .introspectTextField { textField in textField.becomeFirstResponder() }
quelle
Wenn Sie hier gelandet sind, aber mit der Antwort von @Matteo Pacini abgestürzt sind, beachten Sie bitte diese Änderung in Beta 4: Kann nicht der Eigenschaft zugewiesen werden: '$ text' ist für diesen Block unveränderlich :
init(text: Binding<String>) { $text = text }
sollte nutzen:
init(text: Binding<String>) { _text = text }
Wenn Sie das Textfeld zum Ersthelfer in a machen möchten
sheet
, beachten Sie bitte, dass Sie erst anrufen können,becomeFirstResponder
wenn das Textfeld angezeigt wird. Mit anderen Worten, wenn das Textfeld von @Matteo Pacini direkt in densheet
Inhalt eingefügt wird, kommt es zum Absturz.uiView.window != nil
Fügen Sie zur Lösung des Problems eine zusätzliche Überprüfung der Sichtbarkeit des Textfelds hinzu. Konzentrieren Sie sich erst, nachdem es sich in der Ansichtshierarchie befindet: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 ?? "" } } }
quelle
shouldChangeCharactersIn
UITextField-Delegaten verwendete und gleichzeitig den Text in aktualisierteupdateUIView
. Durch Ändern vontextFieldDidChangeSelection
, um den Text in der Bindung zu erfassen, funktioniert das benutzerdefinierte Steuerelement hervorragend! Vielen Dank.sheet
wenn versucht wird, sich zu konzentrieren, bevor die Eingabe angezeigt wird. Ihre Lösung stürzt nicht ab, konzentriert sich aber auch nicht ... Zumindest in meinen Tests. Grundsätzlich wird updateUIView nur einmal aufgerufen.AutoFocusTextField
präsentiert wurde. Ich musste den AnrufbecomeFirstResponder()
in einenDispatchQueue.main.async
Block packen.DispatchQueue.main.async
funktioniert für mich mit Übergangsanimationen definitiv nicht. Wenn Sie beispielsweise NavigationView verwenden und von Ansicht 1 zu Ansicht 2 wechseln (wo comeFirstResponder aufgerufen wird), wird die Übergangsanimation mit Async zweimal abgespielt.Einfache Wrapper-Struktur - Funktioniert wie eine native:
Beachten Sie, dass die Unterstützung für die Textbindung wie in den Kommentaren angegeben hinzugefügt wurde
struct LegacyTextField: UIViewRepresentable { @Binding public var isFirstResponder: Bool @Binding public var text: String public var configuration = { (view: UITextField) in } public init(text: Binding<String>, isFirstResponder: Binding<Bool>, configuration: @escaping (UITextField) -> () = { _ in }) { self.configuration = configuration self._text = text self._isFirstResponder = isFirstResponder } public func makeUIView(context: Context) -> UITextField { let view = UITextField() view.addTarget(context.coordinator, action: #selector(Coordinator.textViewDidChange), for: .editingChanged) view.delegate = context.coordinator return view } public func updateUIView(_ uiView: UITextField, context: Context) { uiView.text = text switch isFirstResponder { case true: uiView.becomeFirstResponder() case false: uiView.resignFirstResponder() } } public func makeCoordinator() -> Coordinator { Coordinator($text, isFirstResponder: $isFirstResponder) } public class Coordinator: NSObject, UITextFieldDelegate { var text: Binding<String> var isFirstResponder: Binding<Bool> init(_ text: Binding<String>, isFirstResponder: Binding<Bool>) { self.text = text self.isFirstResponder = isFirstResponder } @objc public func textViewDidChange(_ textField: UITextField) { self.text.wrappedValue = textField.text ?? "" } public func textFieldDidBeginEditing(_ textField: UITextField) { self.isFirstResponder.wrappedValue = true } public func textFieldDidEndEditing(_ textField: UITextField) { self.isFirstResponder.wrappedValue = false } } }
Verwendung:
struct ContentView: View { @State var text = "" @State var isFirstResponder = false var body: some View { LegacyTextField(text: $text, isFirstResponder: $isFirstResponder) } }
🎁 Bonus: Vollständig anpassbar
LegacyTextField(text: $text, isFirstResponder: $isFirstResponder) { $0.textColor = .red $0.tintColor = .blue }
Diese Methode ist vollständig anpassbar. Zum Beispiel können Sie sehen , wie eine Aktivitätsanzeige in SwiftUI hinzuzufügen mit der gleichen Methode hier
quelle
Wie andere angemerkt haben (z. B. Kommentar von @kipelovets zur akzeptierten Antwort , z. B. Antwort von @ Eonil ), habe ich auch keine der akzeptierten Lösungen für MacOS gefunden. Ich hatte jedoch etwas Glück, als ich NSViewControllerRepresentable verwendet habe , um ein NSSearchField als Ersthelfer in einer SwiftUI-Ansicht anzuzeigen:
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> ) { } }
Beispiel für eine SwiftUI-Ansicht:
struct ContentView: View { @State private var text: String = "" var body: some View { FirstResponderNSSearchFieldRepresentable(text: $text) } }
quelle
Um diese fehlende Funktionalität zu ergänzen, können Sie SwiftUIX mit Swift Package Manager installieren:
Weitere Informationen: https://github.com/SwiftUIX/SwiftUIX
import SwiftUI import SwiftUIX struct ContentView : View { @State var showingTextField = false @State var text = "" var body: some View { return VStack { if showingTextField { CocoaTextField("Placeholder text", text: $text) .isFirstResponder(true) .frame(width: 300, height: 48, alignment: .center) } Button(action: { self.showingTextField.toggle() }) { Text ("Show") } } } }
quelle
📦 ResponderChain
Ich habe dieses kleine Paket für die plattformübergreifende Ersthelferbehandlung erstellt, ohne Ansichten in Unterklassen einzuteilen oder benutzerdefinierte ViewRepresentables in SwiftUI zu erstellen
https://github.com/Amzd/ResponderChain
So wenden Sie es auf Ihr Problem an
SceneDelegate.swift
... // Set the ResponderChain as environmentObject let rootView = ContentView().environmentObject(ResponderChain(forWindow: window)) ...
ContentView.swift
struct ContentView: View { @EnvironmentObject var chain: ResponderChain @State var showingTextField = false @State var text = "" var body: some View { return VStack { if showingTextField { TextField($text).responderTag("field1").onAppear { DispatchQueue.main.async { chain.firstResponder = "field1" } } } Button(action: { self.showingTextField.toggle() }) { Text ("Show") } } } }
quelle
Die ausgewählte Antwort verursacht ein Endlosschleifenproblem mit AppKit. Ich weiß nichts über den UIKit-Fall.
Um dieses Problem zu vermeiden, empfehle ich, die
NSTextField
Instanz direkt freizugeben.import AppKit import SwiftUI struct Sample1: NSViewRepresentable { var textField: NSTextField func makeNSView(context:NSViewRepresentableContext<Sample1>) -> NSView { textField } func updateNSView(_ x:NSView, context:NSViewRepresentableContext<Sample1>) {} }
Sie können das so verwenden.
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()
Dies bricht die reine Wertesemantik, aber abhängig von AppKit bedeutet dies, dass Sie die reine Wertesemantik teilweise aufgeben und sich etwas Schmutz leisten. Dies ist ein magisches Loch, das wir derzeit brauchen, um den Mangel an Ersthelfer-Kontrolle in SwiftUI zu beheben.
Da wir
NSTextField
direkt darauf zugreifen , ist die Einstellung des Ersthelfers eine einfache AppKit-Methode, daher keine sichtbare Problemquelle.Sie können den funktionierenden Quellcode hier herunterladen .
quelle
Da die Responder-Kette nicht für die Verwendung über SwiftUI verfügbar ist, müssen wir sie mit UIViewRepresentable verwenden.
Schauen Sie sich den folgenden Link an, da ich eine Problemumgehung erstellt habe, die ähnlich wie bei UIKit funktioniert.
https://stackoverflow.com/a/61121199/6445871
quelle
Es ist meine Variante der Implementierung, die auf den Lösungen @Mojtaba Hosseini und @Matteo Pacini basiert. Ich bin noch neu in SwiftUI, daher kann ich die absolute Richtigkeit des Codes nicht garantieren, aber es funktioniert.
Ich hoffe es wäre für jemanden hilfreich.
ResponderView: Dies ist eine generische Responder-Ansicht, die mit jeder UIKit-Ansicht verwendet werden kann.
struct ResponderView<View: UIView>: UIViewRepresentable { @Binding var isFirstResponder: Bool var configuration = { (view: View) in } func makeUIView(context: UIViewRepresentableContext<Self>) -> View { View() } func makeCoordinator() -> Coordinator { Coordinator($isFirstResponder) } func updateUIView(_ uiView: View, context: UIViewRepresentableContext<Self>) { context.coordinator.view = uiView _ = isFirstResponder ? uiView.becomeFirstResponder() : uiView.resignFirstResponder() configuration(uiView) } } // MARK: - Coordinator extension ResponderView { final class Coordinator { @Binding private var isFirstResponder: Bool private var anyCancellable: AnyCancellable? fileprivate weak var view: UIView? init(_ isFirstResponder: Binding<Bool>) { _isFirstResponder = isFirstResponder self.anyCancellable = Publishers.keyboardHeight.sink(receiveValue: { [weak self] keyboardHeight in guard let view = self?.view else { return } DispatchQueue.main.async { self?.isFirstResponder = view.isFirstResponder } }) } } } // MARK: - keyboardHeight extension Publishers { static var keyboardHeight: AnyPublisher<CGFloat, Never> { let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification) .map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 } let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification) .map { _ in CGFloat(0) } return MergeMany(willShow, willHide) .eraseToAnyPublisher() } } struct ResponderView_Previews: PreviewProvider { static var previews: some View { ResponderView<UITextField>.init(isFirstResponder: .constant(false)) { $0.placeholder = "Placeholder" }.previewLayout(.fixed(width: 300, height: 40)) } }
ResponderTextField - Dies ist ein praktischer Textfeld-Wrapper um ResponderView.
struct ResponderTextField: View { var placeholder: String @Binding var text: String @Binding var isFirstResponder: Bool private var textFieldDelegate: TextFieldDelegate init(_ placeholder: String, text: Binding<String>, isFirstResponder: Binding<Bool>) { self.placeholder = placeholder self._text = text self._isFirstResponder = isFirstResponder self.textFieldDelegate = .init(text: text) } var body: some View { ResponderView<UITextField>(isFirstResponder: $isFirstResponder) { $0.text = self.text $0.placeholder = self.placeholder $0.delegate = self.textFieldDelegate } } } // MARK: - TextFieldDelegate private extension ResponderTextField { final class TextFieldDelegate: NSObject, UITextFieldDelegate { @Binding private(set) var text: String init(text: Binding<String>) { _text = text } func textFieldDidChangeSelection(_ textField: UITextField) { text = textField.text ?? "" } } } struct ResponderTextField_Previews: PreviewProvider { static var previews: some View { ResponderTextField("Placeholder", text: .constant(""), isFirstResponder: .constant(false)) .previewLayout(.fixed(width: 300, height: 40)) } }
Und wie man das nutzt.
struct SomeView: View { @State private var login: String = "" @State private var password: String = "" @State private var isLoginFocused = false @State private var isPasswordFocused = false var body: some View { VStack { ResponderTextField("Login", text: $login, isFirstResponder: $isLoginFocused) ResponderTextField("Password", text: $password, isFirstResponder: $isPasswordFocused) } } }
quelle
Dies ist ein ViewModifier, der mit Introspect arbeitet . Es funktioniert für AppKit MacOS, Xcode 11.5
struct SetFirstResponderTextField: ViewModifier { @State var isFirstResponderSet = false func body(content: Content) -> some View { content .introspectTextField { textField in if self.isFirstResponderSet == false { textField.becomeFirstResponder() self.isFirstResponderSet = true } } } }
quelle
Wir haben eine Lösung, mit der Sie den Ersthelfer mühelos steuern können.
https://github.com/mobilinked/MbSwiftUIFirstResponder
TextField("Name", text: $name) .firstResponder(id: FirstResponders.name, firstResponder: $firstResponder, resignableUserOperations: .all) TextEditor(text: $notes) .firstResponder(id: FirstResponders.notes, firstResponder: $firstResponder, resignableUserOperations: .all)
quelle
Da SwiftUI 2 noch keine Ersthelfer unterstützt, verwende ich diese Lösung. Es ist schmutzig, kann aber in einigen Anwendungsfällen funktionieren, wenn Sie nur 1
UITextField
und 1 habenUIWindow
.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 } }
quelle