I created a UIViewRepresentable
to wrap UITextField
for SwiftUI, so I can eg change the first responder when the enter key was tapped by the user.
This is my UIViewRepresentable (I removed the first responder code to keep it simple)
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 ?? ""
}
}
}
The first screen of the app has a "Sign in with email" button which pushes MailView that displays a CustomUIKitTextField
and uses a @Published property of an ObservableObject
view model as the TextField's text.
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()
}
}
}
Everything works fine until I push another view like MailView, say eg UsernameView. It is implemented exactly in the same way, but somehow the CustomUIKitTextField
gets an updateUIView
call with an empty string once I finish typing.
There is additional weird behavior like when I wrap MailView and UsernameView in another NavigationView, everything works fine. But that is obviously not the way to fix it, since I would have multiple NavigationViews then.
It also works when using an @State property instead of a @Published property inside a view model. But I do not want to use @State since I really want to keep the model code outside the view.
Is there anybody who faced the same issue or a similar one?
It looks like you're using the wrong delegate method. textFieldDidChangeSelection
will produce some inconsistent results (which is what it sounds like you're dealing with). Instead, I recommend using textFieldDidEndEditing
which will also give you access to the passed in control, but it guarantees that you're getting the object as it is resigning the first responder. This is important because it means you're getting the object after the properties have been changed and it's releasing the responder object.
So, I would change your delegate method as follows:
func textFieldDidEndEditing(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
For more info, see this link for the textFieldDidEndEditing
method: https://developer.apple.com/documentation/uikit/uitextfielddelegate/1619591-textfielddidendediting
And this link for info on the UITextFieldDelegate
object: https://developer.apple.com/documentation/uikit/uitextfielddelegate
EDIT
Based on the comment, if you're looking to examine the text everytime it changes by one character, you should implement this delegate function:
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
}
I also needed a UITextField
representation in SwiftUI, which reacts to every character change, so I went with the answer by binaryPilot84 . While the UITextFieldDelegate
method textField(_:shouldChangeCharactersIn:replacementString:)
is great, it has one caveat -- every time we update the text with this method, the cursor moves to the end of the text . It might be desired behavior. However, it was not for me, so I implemented target-action like so:
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
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.