I'm having some difficulty implementing a custom textfield in SwiftUI. I'm trying to create a password field, however I'm having to dip into UIKit because I need to react when the field is focused, and (unlike with the standard TextField in SwiftUI) there is no onEditingChanged
closure with SecureField.
So I have the following :
struct PasswordField: UIViewRepresentable {
@ObservedObject var viewModel: TextFieldFloatingWithBorderViewModel
func makeUIView(context: UIViewRepresentableContext<PasswordField>) -> UITextField {
let tf = UITextField(frame: .zero)
tf.isUserInteractionEnabled = true
tf.delegate = context.coordinator
return tf
}
func makeCoordinator() -> PasswordField.Coordinator {
return Coordinator(viewModel: viewModel)
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = viewModel.text
uiView.isSecureTextEntry = !viewModel.isRevealed
}
class Coordinator: NSObject, UITextFieldDelegate {
@ObservedObject var viewModel: TextFieldFloatingWithBorderViewModel
init(viewModel: TextFieldFloatingWithBorderViewModel) {
self.viewModel = viewModel
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.viewModel.text = textField.text ?? ""
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
DispatchQueue.main.async {
self.viewModel.isFocused = true
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
DispatchQueue.main.async {
self.viewModel.isFocused = false
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
}
}
We then declare this field as follows within a SwiftUI View:
HStack {
PasswordField(viewModel: viewModel)
Button(action: {
viewModel.toggleReveal()
}) {
viewModel.revealIcon
.foregroundColor(viewModel.isFocused ? .blue : viewModel.hasWarning ? .red: .gray)
}
}
In the TextFieldFloatingWithBorderViewModel viewModel we have a Published var isRevealed
and then the following method called when the button is tapped:
func toggleReveal() {
isRevealed.toggle()
}
Because in the PasswordField we have:
uiView.isSecureTextEntry = !viewModel.isRevealed
This toggles the password field between secure view (i.e. input masked with dots) and standard view. This is working well, except that when the user toggles back to hidden and continues to type the password is being wiped:
I cannot work out why the password is being wiped here, and only when it goes from non secure to secure (not the other way around)