这是我的代码...
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")
}
}
}
}
我想要的是当文本字段变为可见时,使文本字段成为第一响应者(即获得焦点并弹出键盘)。
答案 0 :(得分:9)
对于那些最终在这里遇到但使用@Matteo Pacini的答案崩溃的人,请注意Beta 4:Cannot assign to property: '$text' is immutable中有关此阻止的更改:
init(text: Binding<String>) {
$text = text
}
应使用:
init(text: Binding<String>) {
_text = text
}
如果要使文本字段成为sheet
中的第一响应者,请注意,直到显示文本字段,您才能调用becomeFirstResponder
。换句话说,将@Matteo Pacini的文本字段直接放在sheet
内容中会导致崩溃。
要解决此问题,请添加其他检查uiView.window != nil
,以确保文本字段可见。在焦点集中在视图层次结构中之后:
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 ?? ""
}
}
}
答案 1 :(得分:8)
目前似乎不太可能,但是您可以自己实现类似的功能。
您可以创建一个自定义文本字段并添加一个值,使其成为第一响应者。
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
}
}
}
注意:需要didBecomeFirstResponder
来确保文本字段仅成为第一响应者,而不是SwiftUI
的每次刷新都成为!!
您将像这样使用它...
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。我添加了frame
,因为它的行为不像股票TextField
,这意味着幕后还有更多事情要做。
有关Coordinators
的更多内容,请访问WWDC 19精彩演讲
Integrating SwiftUI
答案 2 :(得分:6)
正如其他人所指出的(例如@kipelovets comment on the accepted answer,例如@Eonil's answer),我还没有找到任何可在macOS上使用的公认解决方案。不过,我很幸运,使用NSViewControllerRepresentable来使NSSearchField成为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>
) {
}
}
示例SwiftUI视图:
struct ContentView: View {
@State private var text: String = ""
var body: some View {
FirstResponderNSSearchFieldRepresentable(text: $text)
}
}
答案 3 :(得分:4)
使用https://github.com/timbersoftware/SwiftUI-Introspect可以:
TextField("", text: $value)
.introspectTextField { textField in
textField.becomeFirstResponder()
}
答案 4 :(得分:3)
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
}
此方法完全适用。例如,您可以使用相同的方法How to add an Activity indicator in SwiftUI
来查看here答案 5 :(得分:3)
macOS 12.0+, Mac 催化剂 15.0+, tvOS 15.0+, watchOS 8.0+
focused(_:)
。专注(_:)
通过将其焦点状态绑定到给定的 Boolean 状态值来修改此视图。
struct NameForm: View {
@FocusState private var isFocued: Bool
@State private var name = ""
var body: some View {
TextField("Name", text: $name)
.focused($isFocued)
Button("Submit") {
if name.isEmpty {
isFocued = true
}
}
}
}
focused(_:equals:)
。专注(_:equals:)
通过将其焦点状态绑定到给定的状态值来修改此视图。
struct LoginForm: View {
enum Field: Hashable {
case usernameField
case passwordField
}
@State private var username = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("Username", text: $username)
.focused($focusedField, equals: .usernameField)
SecureField("Password", text: $password)
.focused($focusedField, equals: .passwordField)
Button("Sign In") {
if username.isEmpty {
focusedField = .usernameField
} else if password.isEmpty {
focusedField = .passwordField
} else {
handleLogin(username, password)
}
}
}
}
}
答案 6 :(得分:2)
不是真正的答案,只是建立在带有方便修饰符的 Casper 出色的解决方案之上 -
struct StartInput: ViewModifier {
@EnvironmentObject var chain: ResponderChain
private let tag: String
init(tag: String) {
self.tag = tag
}
func body(content: Content) -> some View {
content.responderTag(tag).onAppear() {
DispatchQueue.main.async {
chain.firstResponder = tag
}
}
}
}
extension TextField {
func startInput(_ tag: String = "field") -> ModifiedContent<TextField<Label>, StartInput> {
self.modifier(StartInput(tag: tag))
}
}
就用like -
TextField("Enter value:", text: $quantity)
.startInput()
答案 7 :(得分:1)
选择的答案会导致AppKit出现无限循环问题。我不知道UIKit的情况。
为避免该问题,我建议仅直接共享NSTextField
实例。
import AppKit
import SwiftUI
struct Sample1: NSViewRepresentable {
var textField: NSTextField
func makeNSView(context:NSViewRepresentableContext<Sample1>) -> NSView { textField }
func updateNSView(_ x:NSView, context:NSViewRepresentableContext<Sample1>) {}
}
您可以这样使用。
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()
这打破了纯值语义,但是取决于AppKit,意味着您部分放弃了纯值语义,并且会带来一些麻烦。这是我们现在需要解决的一个神奇漏洞,以解决SwiftUI中缺乏第一响应者控制的问题。
当我们直接访问NSTextField
时,设置第一响应者是简单的AppKit方法,因此没有明显的麻烦源。
您可以下载有效的源代码here。
答案 8 :(得分:1)
要填充此缺少的功能,您可以使用Swift Package Manager安装SwiftUIX:
更多信息: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")
}
}
}
}
答案 9 :(得分:0)
扩展@JoshuaKifer 上面的答案,如果您在使用 Introspect 制作文本字段第一响应者时处理导航动画出现故障。使用这个:
import SchafKit
@State var field: UITextField?
TextField("", text: $value)
.introspectTextField { textField in
field = textField
}
.onDidAppear {
field?.becomeFirstResponder()
}
有关此解决方案的更多详细信息 here。
答案 10 :(得分:0)
我制作了这个小包,用于跨平台的第一响应者处理,而无需在SwiftUI中子类化视图或创建自定义ViewRepresentables
https://github.com/Amzd/ResponderChain
SceneDelegate.swift
...
// Set the ResponderChain as environmentObject
let rootView = ResponderChainExample().environmentObject(ResponderChain(forWindow: window))
...
ResponderChainExample.swift
struct ResponderChainExample: View {
@EnvironmentObject var chain: ResponderChain
var body: some View {
VStack(spacing: 20) {
// Show which view is first responder
Text("Selected field: \(chain.firstResponder?.description ?? "Nothing selected")")
// Some views that can become first responder
TextField("0", text: .constant("")).responderTag("0")
TextField("1", text: .constant("")).responderTag("1")
TextField("2", text: .constant("")).responderTag("2")
TextField("3", text: .constant("")).responderTag("3")
// Buttons to change first responder
HStack {
Button("Select 0", action: { chain.firstResponder = "0" })
Button("Select 1", action: { chain.firstResponder = "1" })
Button("Select 2", action: { chain.firstResponder = "2" })
Button("Select 3", action: { chain.firstResponder = "3" })
}
}
.padding()
.onAppear {
// Example how to set first responder on appear
DispatchQueue.main.async {
chain.firstResponder = "0"
}
}
}
}
答案 11 :(得分:0)
我们有一个解决方案,可以轻松控制第一响应者。
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)
答案 12 :(得分:0)
这是与introspect一起使用的ViewModifier。它适用于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
}
}
}
}
答案 13 :(得分:0)
这是我的实现的变体,基于@Mojtaba Hosseini和@Matteo Pacini解决方案。 我还是SwiftUI的新手,所以我不能保证代码的绝对正确性,但是可以正常工作。
我希望这会对某人有所帮助。
ResponderView:这是一个通用响应器视图,可以与任何UIKit视图一起使用。
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-它是围绕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))
}
}
使用方式。
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)
}
}
}
答案 14 :(得分:0)
由于响应链无法通过SwiftUI使用,因此我们必须使用UIViewRepresentable来使用它。
请查看下面的链接,因为我做了一个变通方法,其工作原理与我们使用UIKit的方法类似。
答案 15 :(得分:-1)
就我而言,我想立即将文本字段聚焦,我使用了 .onappear
函数
struct MyView: View {
@FocusState private var isTitleTextFieldFocused: Bool
@State private var title = ""
var body: some View {
VStack {
TextField("Title", text: $title)
.focused($isTitleTextFieldFocused)
}
.padding()
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.isTitleTextFieldFocused = true
}
}
}
}
答案 16 :(得分:-2)
由于 SwiftUI 2 不支持第一响应者,但我使用了此解决方案。它很脏,但是当您只有1 Pattern
和1 UITextField
时,在某些用例中可能会起作用。
UIWindow