在 SwiftUI 中以编程方式自动对焦 TextField

Nil*_*ils 25 xcode modal-dialog textfield swift swiftui

我正在使用模态将名称添加到列表中。当显示模态时,我想自动聚焦 TextField,如下所示:

预览

我还没有找到任何合适的解决方案。

为了做到这一点,是否已经在 SwiftUI 中实现了任何东西?

谢谢你的帮助。

var modal: some View {
        NavigationView{
            VStack{
                HStack{
                    Spacer()
                    TextField("Name", text: $inputText) // autofocus this!
                        .textFieldStyle(DefaultTextFieldStyle())
                        .padding()
                        .font(.system(size: 25))
                        // something like .focus() ??
                    Spacer()
                }
                Button(action: {
                    if self.inputText != ""{
                        self.players.append(Player(name: self.inputText))
                        self.inputText = ""
                        self.isModal = false
                    }
                }, label: {
                    HStack{
                        Text("Add \(inputText)")
                        Image(systemName: "plus")
                    }
                        .font(.system(size: 20))
                })
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.blue)
                    .cornerRadius(10)
                Spacer()
            }
                .navigationBarTitle("New Player")
                .navigationBarItems(trailing: Button(action: {self.isModal=false}, label: {Text("Cancel").font(.system(size: 20))}))
                .padding()
        }
    }
Run Code Online (Sandbox Code Playgroud)

Moj*_*ini 35

iOS 15

\n

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

\n
\n

\xe2\x9a\xa0\xef\xb8\x8f 请注意,如果你想让它在初始时间聚焦,你必须应用延迟。这是 SwiftUI 的一个已知错误。

\n
\n

成为第一响应者(专注)

\n

如果您focused在文本字段上使用修饰符,则可以使它们成为焦点,例如,您可以focusedField在代码中设置该属性以使绑定的文本字段变为活动状态:

\n

演示

\n

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

\n

或通过将变量设置为关闭键盘nil

\n

在此输入图像描述

\n

不要忘记观看WWDC2021 的 SwiftUI 会议中的 Direct 和 Reflect Focus

\n
\n

iOS 13 和 14(和 15)

\n

旧但工作:

\n

简单的包装结构 - 像原生一样工作:

\n

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

\n
struct LegacyTextField: UIViewRepresentable {\n    @Binding public var isFirstResponder: Bool\n    @Binding public var text: String\n\n    public var configuration = { (view: UITextField) in }\n\n    public init(text: Binding<String>, isFirstResponder: Binding<Bool>, configuration: @escaping (UITextField) -> () = { _ in }) {\n        self.configuration = configuration\n        self._text = text\n        self._isFirstResponder = isFirstResponder\n    }\n\n    public func makeUIView(context: Context) -> UITextField {\n        let view = UITextField()\n        view.addTarget(context.coordinator, action: #selector(Coordinator.textViewDidChange), for: .editingChanged)\n        view.delegate = context.coordinator\n        return view\n    }\n\n    public func updateUIView(_ uiView: UITextField, context: Context) {\n        uiView.text = text\n        switch isFirstResponder {\n        case true: uiView.becomeFirstResponder()\n        case false: uiView.resignFirstResponder()\n        }\n    }\n\n    public func makeCoordinator() -> Coordinator {\n        Coordinator($text, isFirstResponder: $isFirstResponder)\n    }\n\n    public class Coordinator: NSObject, UITextFieldDelegate {\n        var text: Binding<String>\n        var isFirstResponder: Binding<Bool>\n\n        init(_ text: Binding<String>, isFirstResponder: Binding<Bool>) {\n            self.text = text\n            self.isFirstResponder = isFirstResponder\n        }\n\n        @objc public func textViewDidChange(_ textField: UITextField) {\n            self.text.wrappedValue = textField.text ?? ""\n        }\n\n        public func textFieldDidBeginEditing(_ textField: UITextField) {\n            self.isFirstResponder.wrappedValue = true\n        }\n\n        public func textFieldDidEndEditing(_ textField: UITextField) {\n            self.isFirstResponder.wrappedValue = false\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

用法:

\n
struct ContentView: View {\n    @State var text = ""\n    @State var isFirstResponder = false\n\n    var body: some View {\n        LegacyTextField(text: $text, isFirstResponder: $isFirstResponder)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

奖励:完全可定制

\n
LegacyTextField(text: $text, isFirstResponder: $isFirstResponder) {\n    $0.textColor = .red\n    $0.tintColor = .blue\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 这实际上并没有回答如何让它自动发生 (6认同)
  • @ma11hew28 我面临着同样的问题,onAppear 的 0.6 延迟对我有用。这似乎是模态视图的问题,在添加模态之前,我在 ZStack 中呈现视图,并且仅使用 0.01 的延迟。我认为在所有情况下都需要延迟。 (4认同)

Ans*_*ngh 20

由于 Responder Chain 不是通过 SwiftUI 呈现来消费的,所以我们必须使用 UIViewRepresentable 来消费它。我已经制定了一个解决方法,它的工作方式类似于我们使用 UIKit 的方式。

 struct CustomTextField: UIViewRepresentable {

   class Coordinator: NSObject, UITextFieldDelegate {

      @Binding var text: String
      @Binding var nextResponder : Bool?
      @Binding var isResponder : Bool?

      init(text: Binding<String>,nextResponder : Binding<Bool?> , isResponder : Binding<Bool?>) {
        _text = text
        _isResponder = isResponder
        _nextResponder = nextResponder
      }

      func textFieldDidChangeSelection(_ textField: UITextField) {
        text = textField.text ?? ""
      }
    
      func textFieldDidBeginEditing(_ textField: UITextField) {
         DispatchQueue.main.async {
             self.isResponder = true
         }
      }
    
      func textFieldDidEndEditing(_ textField: UITextField) {
         DispatchQueue.main.async {
             self.isResponder = false
             if self.nextResponder != nil {
                 self.nextResponder = true
             }
         }
      }
  }

  @Binding var text: String
  @Binding var nextResponder : Bool?
  @Binding var isResponder : Bool?

  var isSecured : Bool = false
  var keyboard : UIKeyboardType

  func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
      let textField = UITextField(frame: .zero)
      textField.isSecureTextEntry = isSecured
      textField.autocapitalizationType = .none
      textField.autocorrectionType = .no
      textField.keyboardType = keyboard
      textField.delegate = context.coordinator
      return textField
  }

  func makeCoordinator() -> CustomTextField.Coordinator {
      return Coordinator(text: $text, nextResponder: $nextResponder, isResponder: $isResponder)
  }

  func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
       uiView.text = text
       if isResponder ?? false {
           uiView.becomeFirstResponder()
       }
  }

}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用这个组件......

struct ContentView : View {

@State private var username =  ""
@State private var password =  ""

// set true , if you want to focus it initially, and set false if you want to focus it by tapping on it.
@State private var isUsernameFirstResponder : Bool? = true
@State private var isPasswordFirstResponder : Bool? =  false


  var body : some View {
    VStack(alignment: .center) {
        
        CustomTextField(text: $username,
                        nextResponder: $isPasswordFirstResponder,
                        isResponder: $isUsernameFirstResponder,
                        isSecured: false,
                        keyboard: .default)
        
        // assigning the next responder to nil , as this will be last textfield on the view.
        CustomTextField(text: $password,
                        nextResponder: .constant(nil),
                        isResponder: $isPasswordFirstResponder,
                        isSecured: true,
                        keyboard: .default)
    }
    .padding(.horizontal, 50)
  }
}
Run Code Online (Sandbox Code Playgroud)

这里 isResponder 是将响应者分配给当前文本字段,而 nextResponder 是做出第一个响应,因为当前文本字段会放弃它。

  • 这不是问题的答案,因为 SwiftUI 是跨平台的,但 UIViewRepresentable 不是。例如,我正在设计一个本机 Mac 应用程序(不是 Mac Catalyst),所以我不能使用这个答案。在 SwiftUI 中需要有一种方法来做到这一点...... (7认同)
  • @Zorayr你不能直接将nil值传递给它,因为它是一个绑定变量,所以你需要一个带有nil值的常量状态对象才能使其工作。尝试使用 '.constant(nil)' 而不是简单的 nil。这将消除错误。 (2认同)

Zor*_*ayr 7

SwiftUIX 解决方案

这非常容易,SwiftUIX我很惊讶更多人不知道这一点。

  1. 通过Swift Package Manager安装 SwiftUIX 。
  2. 在您的代码中,import SwiftUIX.
  3. 现在您可以使用CocoaTextField代替TextField来使用功能.isFirstResponder(true)
CocoaTextField("Confirmation Code", text: $confirmationCode)
    .isFirstResponder(true)
Run Code Online (Sandbox Code Playgroud)


Sko*_*oua 6

我试图根据以前的答案使其变得简单,这使得视图出现时键盘出现,没有其他。刚刚在iOS 16上测试,确实自动出现,无需设置延迟。

struct MyView: View {
    @State private var answer = ""
    @FocusState private var focused: Bool // 1. create a @FocusState here
    
    var body: some View {
        VStack {
            TextField("", text: $answer)
                .focused($focused) // 2. set the binding here
        }
        .onAppear {
            focused = true // 3. pop the keyboard on appear
        }
    }
}
Run Code Online (Sandbox Code Playgroud)