Search code examples
iosswiftuitextfield

Changing isSecureTextEntry is being used on a wrong textField


I made a subclass of a UIView with a text field and a button which allows to configure if text field is secured via isSecureTextEntry property. I am using two instances of that view one for setting a password and another one for confirming it in a view controller like this

let passwordTextField = PasswordTextFieldView(placeholder: "New password")
let confirmPasswordTextField = PasswordTextFieldView(placeholder: "Confirm password")

Text field subclass code

final class PasswordTextFieldView: UIView {
    lazy var textField: UITextField = {
        let textField = UITextField()
        textField.textColor = .black
        textField.placeholder = placeholder
        textField.textAlignment = .left
        textField.textContentType = .password
        textField.autocorrectionType = .no
        textField.isSecureTextEntry = true
        return textField
    }()
    
    private let securedButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "eye.slash.fill")?.withRenderingMode(.alwaysTemplate), for: .normal)
        button.tintColor = .gray
        button.addTarget(self, action: #selector(securedButtonTapped), for: .touchUpInside)
        return button
    }()
    
    private var isSecured: Bool = true
    
    var placeholder: String
    
    required init(placeholder: String) {
        self.placeholder = placeholder
        super.init(frame: CGRect.zero)
        // Layout views
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    @objc func securedButtonTapped() {
        isSecured.toggle()
        securedButton.setImage(UIImage(systemName: isSecured ? "eye.slash.fill" : "eye.fill"), for: .normal)
        textField.isSecureTextEntry = isSecured
    }
}

So the problem is, that tapping a button changes isSecureTextEntry on a textField which is being edited. How to fix it? enter image description here


Solution

  • When you create your let variable with a self calling block, you shouldn't use self as it's not determined at that moment. You can add print to see it's value, it's not your view, because the view is not yet created.

    Sometimes it'll be correct value at the end(I tried running your code and it run fine on my simulator), and sometimes it's not.

    You have two options in such cases:

    1. Replace let with lazy var. In this case self if always gonna be determined
    private lazy var securedButton: UIButton = {
       ...
    }
    
    
    1. Move adding target to your init. If you're using many inits don't forget to add to all of them, preferably moving to an other function. I prefer this way to make sure all my props are let if I don't need to change it. Like this:
    required init(placeholder: String) {
        self.placeholder = placeholder
        super.init(frame: CGRect.zero)
        initialize()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initialize()
        // I know you're not using this initializer, just in case you'd need in future
    }
    
    private func initialize() {
        securedButton.addTarget(self, action: #selector(securedButtonTapped), for: .touchUpInside)
    }