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

SwiftUI: How to make TextField become first responder?

这是我的 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 {

    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

测试于 Xcode 11.4

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

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

iOS 15

有一个名为 @FocusState 的新包装器,用于控制键盘和聚焦键盘的状态('aka' firstResponder)。

成为第一响应者(专注)

如果您在文本字段上使用 focused 修饰符,您可以使它们成为焦点:

辞去第一响应者(关闭键盘)

或通过将变量设置为 nil:

来关闭键盘



iOS 13 岁及以上:老了但还能工作!

简单的包装器结构 - 像本地人一样工作:

请注意 根据评论中的要求添加了文本绑定支持

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.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        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
        configuration(uiView)
        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
        }
    }
}

用法:

struct ContentView: View {
    @State var text = ""
    @State var isFirstResponder = false

    var body: some View {
        LegacyTextField(text: $text, isFirstResponder: $isFirstResponder)
    }
}

奖励:完全可定制

LegacyTextField(text: $text, isFirstResponder: $isFirstResponder) {
    [=12=].textColor = .red
    [=12=].tintColor = .blue
}

此方法完全适用。例如,您可以看到 with the same method

使用SwiftUI-Introspect,您可以:

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

你可以像这样使用它。

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

正如其他人所指出的(例如 , e.g. ), I have also not found any of the accepted solutions to work on macOS. I've had some luck, however, using NSViewControllerRepresentable to get a 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)
  }
}

由于无法通过 SwiftUI 使用 Responder Chain,因此我们必须使用 UIViewRepresentable 使用它。

请看下面的 link,因为我已经做了一个解决方法,它的工作方式与我们过去使用 UIKit 的方式类似。

这是我的实现变体,基于@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 { ([=10=].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)) {
            [=10=].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) {
            [=11=].text = self.text
            [=11=].placeholder = self.placeholder
            [=11=].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)
        }
    }
}

这是一个适用于 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
            }
         }
      }
   }

由于 SwiftUI 2 不支持第一响应者,但我使用此解决方案。它很脏,但可能适用于只有 1 UITextField 和 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 { [=10=] as? UITextField }.first ??
            subviews.compactMap { [=10=].textField }.first
    }
}

要填补这个缺失的功能,您可以使用 Swift 包管理器安装 SwiftUIX:

  1. 在 Xcode 中,打开您的项目并导航至文件 → Swift 包 → 添加包依赖项...
  2. 粘贴存储库 URL (https://github.com/SwiftUIX/SwiftUIX) 并单击“下一步”。
  3. 对于规则,select 分支(分支设置为主)。
  4. 单击“完成”。
  5. 打开项目设置,将SwiftUI.framework添加到链接框架和库,将状态设置为可选。

更多信息: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")
            }
        }
    }
}

我们有一个解决方案,可以毫不费力地控制第一响应者。

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)

响应链

我在 iOS 13+

上为 跨平台第一响应者处理制作了这个小包,没有子类化视图或在 SwiftUI 中制作自定义 ViewRepresentables

https://github.com/Amzd/ResponderChain

如何将其应用于您的问题

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

不是真正的答案,只是在 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))
    }
}

就像-

一样使用
TextField("Enter value:", text: $quantity)
    .startInput()

iOS 15.0+

macOS 12.0+,

Mac 催化剂 15.0+,

tvOS 15.0+,

watchOS 8.0+

如果您只有一个 TextField,请使用 focused(_:)

focused(_:)

Modifies this view by binding its focus state to the given Boolean state value.

struct NameForm: View {
    
    @FocusState private var isFocused: Bool
    
    @State private var name = ""
    
    var body: some View {
        TextField("Name", text: $name)
            .focused($isFocused)
        
        Button("Submit") {
            if name.isEmpty {
                isFocued = true
            }
        }
    }
}

如果您有多个 TextField,请使用 focused(_:equals:)

focused(_:equals:)

Modifies this view by binding its focus state to the given state value.

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

SwiftUI 文档


更新

我在 xCode version 13.0 beta 5 (13A5212g) 中对此进行了测试。有效

在我的例子中,我想立即聚焦一个文本字段,我使用了 .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
            }
            
        }
    }
}

扩展@Jos​​huaKifer 上面的回答,如果您在使用 Introspect 使文本字段成为第一响应者时处理导航动画出现故障。使用这个:

import SchafKit

@State var field: UITextField?

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

有关此解决方案的更多详细信息 here

我采用了一些不同的方法 - 我基于 UIView 制作了它,而不是基于 UITextFieldUIViewRepresentable 并使用 [=16= 将其插入 SwiftUI 视图层次结构]修饰符。在 UIView 中,我添加了逻辑以在子视图和父视图中找到 canBecomeFirstResponder 的第一个视图。

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


这是一个如何使用它使 TextField 自动对焦的示例(+ 奖金利用 @FocusState 新 iOS 15 api)。

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

内容视图示例:

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

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

正确的 SwiftUI 方法是如上所述使用 @FocusState。但是,此 API 仅适用于 iOS 15。如果您使用的是 iOS 14 或 iOS 13,则可以使用模仿 Apple [=27] 的 Focuser 库=].

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

这是一个示例代码。您会注意到 API 看起来几乎与 Apple 完全一样,但是 Focuser 还提供使用键盘将第一响应者移动到链下,这非常方便。

如果您对 @JoshuaKifer@ahaze 的回复有任何疑问, 我通过 在父 class 上使用修饰符而不是在 TextField 本身上解决了我的问题。

我在做什么:

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

我是如何解决问题的:

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

为了清楚起见,我将把父结构的定义放在下面

   struct YourParentStruct: View {
       @Binding var searchInput: String

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

最简单 形式 iOS 13(不使用任何第三方 sdk/repo ,或者如果您还没有升级到 iOS 14 以使用 focus 修饰符)

struct CustomTextField: UIViewRepresentable {
            
    func makeUIView(context: Context) -> UITextField {
         UITextField(frame: .zero)
    }
    
    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.becomeFirstResponder()
    }
}

用法:

struct ContentView : View {

    var body: some View {
        CustomTextField()
            .frame(width: 300, height: 50)
            .background(Color.red)
    }
}

SwiftUI

struct ContentView: View {
enum Field {
    case firstTextfield
    case secondTextfield
    case lastTextfield
}

@State private var firstTextfield = ""
@State private var secondTextfield = ""
@State private var lastTextfield = ""
@FocusState private var focusedField: Field?

var body: some View {
    VStack {
        TextField("Enter anything on first textfield", text: $firstTextfield)
            .focused($focusedField, equals: .firstTextfield)
            .submitLabel(.next)

        TextField("Enter anything on second textfield", text: $secondTextfield)
            .focused($focusedField, equals: .secondTextfield)
            .submitLabel(.next)

        TextField("Enter anything on last textfield", text: $lastTextfield)
            .focused($focusedField, equals: .lastTextfield)
            .submitLabel(.join)
    }
    .onSubmit {
        switch focusedField {
        case .firstTextfield:
            focusedField = .secondTextfield
        case .secondTextfield:
            focusedField = .lastTextfield
        default:
            focusedField = nil
        }
    }
  }
}

描述:添加一个带有文本字段案例的枚举,以及一​​个 属性 包装在具有该枚举类型的 @FocusState 中。添加 focused(_:equals:) 修饰符以具有绑定值,等于枚举案例。现在,您可以将 focusedField 更改为您希望光标所在的任何文本字段,或者通过将 nil 分配给 focusedField 来退出第一响应者。