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)
CodePudding user response:
You already have the @FocusState
for this. You can use it like any state variable. Here is some code where I put a yellow background on a TextField
and conditionally controlled it with the @FocusState
variable:
struct HighlightWhenFocused: View {
@State private var password: String = ""
@FocusState var passwordFocused: Bool
var body: some View {
PasswordView(password: $password)
.focused($passwordFocused)
.background(
Color.yellow
.opacity(passwordFocused ? 1 : 0)
)
}
}
struct PasswordView: View {
@Binding var password: String
@State private var secured = true
var body: some View {
HStack{
Text("Password:")
if secured{
SecureField("",text:$password)
}
else{
TextField("",text: $password)
}
Button {
secured.toggle()
} label: {
Image(systemName: secured ? "eye.slash" : "eye")
}
.buttonStyle(BorderlessButtonStyle())
}
}
}
Edited it to make it a secure password solution. Please note that when you switch between viewable/non-viewable, the fields lose focus because you tapped outside of them.