[英]SwiftUI Custom TextField with UIViewRepresentable Issue with ObservableObject and pushed View
我创建了一个UIViewRepresentable
来为 SwiftUI 包装UITextField
,因此我可以在用户点击回车键时更改第一响应者。
这是我的 UIViewRepresentable(为了简单起见,我删除了第一响应者代码)
struct CustomUIKitTextField: UIViewRepresentable {
@Binding var text: String
var placeholder: String
func makeUIView(context: UIViewRepresentableContext<CustomUIKitTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomUIKitTextField>) {
uiView.text = text
uiView.setContentHuggingPriority(.defaultHigh, for: .vertical)
uiView.setContentCompressionResistancePriority(.required, for: .vertical)
}
func makeCoordinator() -> CustomUIKitTextField.Coordinator {
Coordinator(parent: self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomUIKitTextField
init(parent: CustomUIKitTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
}
应用程序的第一个屏幕有一个“使用电子邮件登录”按钮,该按钮会推送显示 CustomUIKitTextField 的CustomUIKitTextField
,并使用ObservableObject
视图模型的 @Published 属性作为 TextField 的文本。
struct MailView: View {
@ObservedObject var viewModel: MailSignUpViewModel
var body: some View {
VStack {
CustomUIKitTextField(placeholder: self.viewModel.placeholder,
text: self.$viewModel.mailAddress)
.padding(.top, 30)
.padding(.bottom, 10)
NavigationLink(destination: UsernameView(viewModel: UsernameSignUpViewModel())) {
Text("Next")
}
Spacer()
}
}
}
一切正常,直到我推送另一个视图,如 MailView,例如 UsernameView。 它以完全相同的方式实现,但不知何故,一旦我完成输入, CustomUIKitTextField
就会以空字符串获取updateUIView
调用。
还有一些奇怪的行为,例如当我将 MailView 和 UsernameView 包装在另一个 NavigationView 中时,一切正常。 但这显然不是解决问题的方法,因为那时我会有多个 NavigationView。
它也适用于在视图模型中使用 @State 属性而不是 @Published 属性。 但我不想使用@State,因为我真的想将模型代码保留在视图之外。
有没有人遇到相同或类似的问题?
看起来您使用了错误的委托方法。 textFieldDidChangeSelection
会产生一些不一致的结果(这听起来像是你正在处理的)。 相反,我建议使用textFieldDidEndEditing
,它还可以让您访问传入的控件,但它保证您在退出第一响应者时获得对象。 这很重要,因为这意味着您在属性更改后获取对象并且它正在释放响应者对象。
所以,我会改变你的委托方法如下:
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
有关详细信息,请参阅textFieldDidEndEditing
方法的此链接: https ://developer.apple.com/documentation/uikit/uitextfielddelegate/1619591-textfielddidendediting
此链接可获取有关UITextFieldDelegate
对象的信息: https ://developer.apple.com/documentation/uikit/uitextfielddelegate
编辑
根据评论,如果您希望在每次更改一个字符时检查文本,您应该实现这个委托函数:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// User pressed the delete key
if string.isEmpty {
// If you want to update your query string here after the delete key is pressed, you can rebuild it like we are below
return true
}
//this will build the full string the user intends so we can use it to build our search
let currentText = textField.text ?? ""
let replacementText = (currentText as NSString).replacingCharacters(in: range, with: string)
// You can set your parent.text here if you like, or you can fire the search function as well
// When you're done though, return true to indicate that the textField should display the changes from the user
return true
}
我还需要 SwiftUI 中的UITextField
表示,它会对每个字符的变化做出反应,所以我选择了binaryPilot84的答案。 虽然UITextFieldDelegate
方法textField(_:shouldChangeCharactersIn:replacementString:)
很棒,但它有一个警告——每次我们用这个方法更新文本时,光标都会移动到文本的末尾。 这可能是期望的行为。 但是,它不适合我,所以我像这样实现了目标操作:
public func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.addTarget(context.coordinator, action: #selector(context.coordinator.textChanged), for: .editingChanged)
return textField
}
public final class Coordinator: NSObject {
@Binding private var text: String
public init(text: Binding<String>) {
self._text = text
}
@objc func textChanged(_ sender: UITextField) {
guard let text = sender.text else { return }
self.text = text
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.