我正在尝试在 SwiftUI 中创建一个系统,让我可以控制哪个文本字段是第一响应者,以便我可以轻松创建响应者链。
我创建了一个 UIViewRepresentable
,它使用布尔类型获取 Binding
的环境值,并且我正在根据这些函数 textFieldDidBeginEditing
和 textFieldDidEndEditing
更改状态。>
struct TextField: UIViewRepresentable {
@Binding var text: String
@Environment(\.isSecureField) var isSecureField: Bool
@Environment(\.isFirstResponder) var isFirstResponder: Binding<Bool>?
@Environment(\.placeholder) var placeholder: String?
@Environment(\.keyboardType) var keyboardType: UIKeyboardType
@Environment(\.returnKeyType) var returnKeyType: UIReturnKeyType
@Environment(\.textContentType) var contentType: UITextContentType?
@Environment(\.autoCapitalization) var autoCapitalization: Bool
@Environment(\.autoCorrection) var autoCorrection: Bool
@Environment(\.font) var font: Font?
let onCommit: (() -> Bool)?
init(
text: Binding<String>,
onCommit:(() -> Bool)? = nil) {
self._text = text
self.onCommit = onCommit
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
syncValues(context: context, textField: textField)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
syncValues(context: context, textField: uiView)
if let isFirstReposnderValue = isFirstResponder?.wrappedValue {
switch isFirstReposnderValue {
case true:
uiView.becomeFirstResponder()
case false:
uiView.resignFirstResponder()
}
}
}
private func syncValues(context: Context, textField: UITextField) {
textField.text = text
textField.isSecureTextEntry = isSecureField
textField.textContentType = contentType
textField.keyboardType = keyboardType
textField.placeholder = placeholder
textField.font = UIFont.preferredFont(from: font)
textField.autocapitalizationType = autoCapitalization ? .sentences : .none
textField.returnKeyType = returnKeyType
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textField.autocorrectionType = autoCorrection ? .yes : .no
}
func makeCoordinator() -> Coordinator {
return Coordinator( parent: self )
}
class Coordinator: NSObject, UITextFieldDelegate {
private let parent: TextField
init(parent: TextField) {
self.parent = parent
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return parent.onCommit?() ?? true
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.$text.wrappedValue = textField.text ?? ""
}
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.isFirstResponder?.wrappedValue = true
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.isFirstResponder?.wrappedValue = false
}
}
}
此外,我为具有布尔类型的 Binding 创建了一个新的构造函数,以便我可以使用 Hashable(如枚举),因此我可以有两个以上的状态并且没有真或假。
extension Binding where Value == Bool {
init<V: Hashable>(tag: V, selection: Binding<V?>) {
self.init { () -> Value in
selection.wrappedValue == tag
} set: { (newValue) in
selection.wrappedValue = newValue ? tag : nil
}
}
}
这是我如何使用它的示例:
class TestLoginViewModel:ObservableObject {
@Published var email: String = ""
@Published var password: String = ""
}
struct TestLoginView: View {
enum Field {
case email
case password
}
@StateObject private var viewModel: TestLoginViewModel
@State private var activeField: Field?
init(viewModel: TestLoginViewModel = .init()) {
self._viewModel = StateObject(wrappedValue: viewModel)
}
var body: some View {
VStack(spacing: 8) {
TextField(text: $viewModel.email, onCommit: { () -> Bool in
activeField = .password
return true
})
.placeholder(L10n.email)
.isFirstReponder(.init(tag: .email, selection: $activeField))
.returnKeyType(.next)
.keyboard(.emailAddress)
TextField(text: $viewModel.password, onCommit: { () -> Bool in
activeField = nil
return true
})
.placeholder(L10n.password)
.isSecureField(true)
.isFirstReponder(.init(tag: .password, selection: $activeField))
.returnKeyType(.send)
.keyboard(.emailAddress)
}
}
}
但我得到:
<块引用>在视图更新期间修改状态,这将导致未定义的行为。