首页 > 解决方案 > 在 SwiftUI 中重新关注第一响应者

问题描述

我知道如何UIViewRepresentable在 SwiftUI 中创建一个以启用 TextField 的第一响应者功能:

struct FirstResponderTextField: UIViewRepresentable {
    var placeholder: String
    @Binding var text: String

    func makeUIView(context: UIViewRepresentableContext<FirstResponderTextField>) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<FirstResponderTextField>) {
        uiView.placeholder = placeholder
        uiView.text = text

        if (!uiView.isFirstResponder) {
            uiView.becomeFirstResponder()
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        var firstResponderTextField: FirstResponderTextField

        init(_ firstResponderTextField: FirstResponderTextField) {
            self.firstResponderTextField = firstResponderTextField
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            firstResponderTextField.text = textField.text ?? ""
        }
    }
}

我的问题是试图“重新聚焦”这个自定义文本字段。因此,虽然这个文本字段在我ContentView的初始化时确实获得了焦点,但我想知道在它失去焦点之后如何以编程方式重新聚焦这个文本字段。

这是我的ContentView

struct ContentView: View {
    @State var textField1: String = ""
    @State var textField2: String = ""

    var body: some View {
        Form {
            Section {
                FirstResponderTextField(placeholder: "Text Field 1", text: $textField1)
                TextField("Text Field 2", text: $textField2)
            }
            Section {
                Button(action: {
                    // ???
                }, label: {
                    Text("Re-Focus Text Field 1")
                })
            }
        }
    }
}

这是我尝试过的。我想也许我可以创建一个@State可以控制的变量FirstResponderTextField,所以我继续更改我的结构如下:

struct ContentView: View {
    @State var textField1: String = ""
    @State var textField2: String = ""
    @State var isFocused: Bool = true

    var body: some View {
        Form {
            Section {
                FirstResponderTextField(placeholder: "Text Field 1", text: $textField1, isFocused: $isFocused)
                TextField("Text Field 2", text: $textField2)
            }
            Section {
                Button(action: {
                    self.isFocused = true
                }, label: {
                    Text("Re-Focus Text Field 1")
                })
            }
        }
    }
}

struct FirstResponderTextField: UIViewRepresentable {
    var placeholder: String
    @Binding var text: String
    @Binding var isFocused: Bool

    func makeUIView(context: UIViewRepresentableContext<FirstResponderTextField>) -> UITextField {
        let textField = UITextField()
        textField.delegate = context.coordinator
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<FirstResponderTextField>) {
        uiView.placeholder = placeholder
        uiView.text = text

        if (isFocused) {
            uiView.becomeFirstResponder()
            isFocused = false
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject, UITextFieldDelegate {
        var firstResponderTextField: FirstResponderTextField

        init(_ firstResponderTextField: FirstResponderTextField) {
            self.firstResponderTextField = firstResponderTextField
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            firstResponderTextField.text = textField.text ?? ""
        }
    }
}

它似乎没有工作。我的意思是,当我第一次单击按钮时它起作用,但之后停止工作。

另外,我现在收到此警告:

在视图更新期间修改状态,这将导致未定义的行为。

是否可以创建一个UIViewRepresentable可以随时重新聚焦的内容?

标签: iosswiftxcodeuikit

解决方案


您不应该在 FirstResponderTextField 中将 isFocused 设置为 false。

相反,在 FirstResponderTextField 中,观察 isFocused 绑定中的值变化,并相应地将您的控件设置为是否为第一响应者。

我在 github 上的这个 gist 中使用与 TextFieldState 对应的 ObserableObject 为您创建了一个解决方案

为了将来参考,这是我改变的:

  • 而不是@State var isFocused: Bool = true在 ContentView 中,使用@ObservedObject var textFieldState = TextFieldState()
  • @ObservedObject var state: TextFieldState在 FirstResponderTextField 中有一个属性
  • 只需在以下位置执行此操作FirstResponderTextField.updateUIView
    if state.isFirstResponder {
        uiView.becomeFirstResponder()
    }

推荐阅读