AppKit 上的 SwiftUI - 将焦点设置到弹出窗口中的 TextField

SwiftUI on AppKit - Set Focus to a TextField in a popover

在下面的 AppKit 示例中,我希望第一个文本字段在弹出窗口出现时具有(键盘)焦点。
我怎样才能

struct ContentView: View {
  @State var showPopup = false
  @State var text = ""
  
  var body: some View {
    VStack{
      Text("Hello, world!")
      .padding()
      Button("show Popup") { showPopup = true}
    }
    .frame(width: 300, height: 300)
    .padding()
    .popover( isPresented: $showPopup,
              arrowEdge: .trailing
    ) {
      VStack{
        Text("Popup")
        TextField("enter Text",text:$text )
      }
      .padding()
    }
  }
}

I needed to do this in one of my projects myself。 首先,我将 NSViewController 子类化,其中视图在视图出现时成为第一响应者(聚焦)。

class FocusedTextFieldViewController: NSViewController, NSTextFieldDelegate {
    @Binding var text: String
    
    init(text: Binding<String>) {
        _text = text
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }

    var textField: NSTextField!
    
    override func loadView() {
        // Set up the text field.
        textField = NSTextField()
        
        // Set the text field's delegate
        textField.delegate = self
        
        // Set an initial text value.
        textField.stringValue = text
        
        // Set the view to the new text field.
        view = textField
    }
    
    override func viewDidAppear() {
        // Make the view the first responder.
        view.window?.makeFirstResponder(view)
    }
    
    func controlTextDidChange(_ obj: Notification) {
        // Update the text.
        text = textField.stringValue
    }
}

然后,我使用 NSViewControllerRepresentable 在 SwiftUI 中显示 NSViewController。

struct FocusedTextField: NSViewControllerRepresentable {
    @Binding var text: String
    
    func makeNSViewController(context: Context) -> some FocusedTextFieldViewController {
        return FocusedTextFieldViewController(text: $text)
    }
    
    func updateNSViewController(_ nsViewController: NSViewControllerType, context: Context) {
        let textField = nsViewController.textField
        textField?.stringValue = text
    }
}

现在,您可以在 SwiftUI 中使用“FocusedTextField”了。

struct ContentView: View {
    @State private var popoverIsPresented = false
    @State private var text = ""
    var body: some View {
        Button("Show Popover") {
            // Present the popover.
            popoverIsPresented.toggle()
        }
        .popover(isPresented: $popoverIsPresented, content: {
            FocusedTextField(text: $text)
        })
    }
}

如果您想取消文本字段的焦点,您可以将其添加到 FocusedTextFieldViewController 的 loadView 函数中,然后使用通知中心再次取消文本字段的焦点。

override func loadView() {
    // Set up the text field.
    textField = NSTextField()
    
    // Set the text field's delegate
    textField.delegate = self
    
    // Set an initial text value.
    textField.stringValue = text
    
    // Set the view to the new text field.
    view = textField
    
    // Set up a notification for unfocusing the text field.
    NotificationCenter.default.addObserver(forName: Notification.Name("UnfocusTextField"), object: nil, queue: nil, using: { _ in
        // Unfocus the text field.
        self.view.window?.resignFirstResponder()
    })
}

然后你可以在任何时候想要取消文本字段的焦点时执行此操作。

NotificationCenter.default.post(name: Notification.Name("UnfocusTextField"), object: nil)

但如果文本字段当前处于活动状态,这可能不起作用。