SwiftUI:如何让TextField成为第一响应者?

SwiftUI:如何让TextField成为第一响应者?,swift,textfield,swiftui,first-responder,Swift,Textfield,Swiftui,First Responder,这是我的SwiftUI代码: struct ContentView : View { @State var showingTextField = false @State var text = "" var body: some View { return VStack { if showingTextField { TextField($text) }

这是我的
SwiftUI
代码:

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

我想要的是当文本字段变为可见时,使文本字段成为第一响应者(即接收焦点并弹出键盘)。

Swift UI 3

从Xcode 13开始,您可以使用
focused
修改器使视图成为第一响应者


Swift UI 1/2

目前似乎不可能,但您可以自己实现类似的功能

您可以创建一个自定义文本字段并添加一个值,使其成为第一响应者

struct CustomTextField:UIViewRepresentable{
类协调器:NSObject,UITextFieldDelegate{
@绑定变量文本:字符串
var didBecomeFirstResponder=false
init(文本:绑定){
_文本=文本
}
func textField didchangeSelection(textField:UITextField){
text=textField.text??“”
}
}
@绑定变量文本:字符串
var isFirstResponder:Bool=false
func makeUIView(上下文:UIViewRepresentableContext)->UITextField{
设textField=UITextField(帧:.0)
textField.delegate=context.coordinator
返回文本字段
}
func makeCoordinator()->CustomTextField.Coordinator{
退货协调员(文本:$text)
}
func updateUIView(uiView:UITextField,context:UIViewRepresentableContext){
uiView.text=文本
如果是第一响应者&&!context.coordinator.didBecomeFirstResponder{
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder=true
}
}
}
注意:
didBecomeFirstResponder
用于确保文本字段仅成为firstresponder一次,而不是每次由
SwiftUI刷新时都成为firstresponder

你会像这样使用它

struct ContentView:View{
@状态变量文本:String=“”
var body:一些观点{
CustomTextField(text:$text,isFirstResponder:true)
.框架(宽:300,高:50)
.背景(颜色.红色)
}
}
另外,我添加了一个
框架
,因为它不像股票
文本字段
,这意味着幕后会有更多的事情发生

有关协调人的更多信息,请参见本次精彩的WWDC 19演讲:


在Xcode 11.4上测试

对于任何最终来到这里但使用@Matteo Pacini的答案面临崩溃的人,请注意beta 4中的此更改:关于此块:

init(文本:绑定){
$text=text
}
应使用:

init(文本:绑定){
_文本=文本
}

如果您想让文本字段成为
工作表中的第一响应者
,请注意,在显示文本字段之前,您不能调用
becomeFirstResponder
。换句话说,将@Matteo Pacini的文本字段直接放在
工作表中会导致崩溃

要解决此问题,请添加一个附加检查
uiView.window!=零
表示文本字段的可见性。仅在其位于视图层次结构中后才聚焦:

struct AutoFocusTextField:UIViewRepresentable{
@绑定变量文本:字符串
func makeCoordinator()->Coordinator{
协调员(自我)
}
func makeUIView(上下文:UIViewRepresentableContext)->UITextField{
设textField=UITextField()
textField.delegate=context.coordinator
返回文本字段
}
func updateUIView(uiView:UITextField,context:
UIViewRepresentableContext){
uiView.text=文本
如果uiView.window!=nil,!uiView.isFirstResponder{
uiView.becomeFirstResponder()
}
}
类协调器:NSObject,UITextFieldDelegate{
变量父项:AutoFocusTextField
init(u0; autoFocusTextField:autoFocusTextField){
self.parent=autoFocusTextField
}
func textField didchangeSelection(textField:UITextField){
parent.text=textField.text??“”
}
}
}
iOS 15 有一个名为
@FocusState
的新包装器,它控制键盘和聚焦键盘的状态(“aka”firstResponder)

Becaome第一响应者(专注) 如果对文本字段使用
focused
修饰符,可以使其成为焦点:

辞职第一响应者(解雇键盘) 或者通过将变量设置为
nil
,关闭键盘:



老而活: 简单包装结构-与本机结构类似: 注意根据评论中的要求添加了文本绑定支持

使用,您可以执行以下操作:

TextField(“,text:$value)
.introspectTextField{textField in
textField.becomeFirstResponder()
}

选择的答案会导致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>) {}
}
这打破了纯值语义,但依赖AppKit意味着您部分放弃了纯值语义,并将承受一些肮脏。这是一个魔术洞,我们现在需要处理缺乏第一反应控制在SwiftUI

由于我们直接访问
NSTextField
,设置第一响应程序是简单的AppKit方式,因此没有可见的故障源

您可以下载可用的源代码。

正如其他人所指出的(例如),我也没有找到任何可用于macOS的公认解决方案。不过,我还是有点运气,使用它可以在快捷界面视图中以第一响应者的身份出现:

导入可可粉
导入快捷键
类FirstResponderNSSearchFieldController:NSViewController{
@绑定变量文本:字符串
init(文本:绑定){
self.\u text=文本
super.init(nibName:nil,bundle:nil)
}
必需初始化?(编码器:NSCoder){
fatalError(“init(coder:)尚未实现
struct ContentView: View {
    @State var text = ""
    @State var isFirstResponder = false

    var body: some View {
        LegacyTextField(text: $text, isFirstResponder: $isFirstResponder)
    }
}
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()
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))
    }
}
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)
        }
    }
}
    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
            }
         }
      }
   }
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
    }
}
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")
            }
        }
    }
}
TextField("Name", text: $name)
    .firstResponder(id: FirstResponders.name, firstResponder: $firstResponder, resignableUserOperations: .all)

TextEditor(text: $notes)
    .firstResponder(id: FirstResponders.notes, firstResponder: $firstResponder, resignableUserOperations: .all)
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))
    }
}
TextField("Enter value:", text: $quantity)
    .startInput()
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
            }
        }
    }
}

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