Home > Enterprise >  Swift: TextField clearing itself after isSecureTextEntry set
Swift: TextField clearing itself after isSecureTextEntry set

Time:03-10

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:

enter image description here

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.

  • Related