iOS 12 Suggested strong password textfield delegate callback for Choose My Own Password
Asked Answered
D

6

10

In iOS 12 I have a new password textfield for a sign up flow and I want the system to suggest a strong one. I also have a button that enables and disables based on the delegate method and I do some changes etc.

textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String)

This works good for enabling it when the user taps Use Strong Password. But I don't seem to get a delegate callback for when a user might tap Choose My Own Password and a s a result my button enable/disable logic never gets a chance to execute, allowing someone to sign up with a blank password.

Suggested password

Any ideas on what I might have t do to get a callback when the user taps Choose my own password? Any help is greatly appreciated.

Diaster answered 1/12, 2018 at 0:10 Comment(0)
C
3

iOS 13 introduced a new delegate method

func textFieldDidChangeSelection(_ textField: UITextField) {

}

This gets called when the strong password is filled in and removed. Again it is only available in iOS 13.

It is also called when text changes or is cleared so be aware if you are calling shouldChangeCharactersIn or added an action using UIControl.Event.editingChanged both methods would respond

Covenantor answered 4/3, 2020 at 16:19 Comment(0)
J
2

I faced the same issue, but the only thing I found out was providing callbacks was the viewDidLayoutSubviews delegate of the UIView

So what I did is use this:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        if passwordTextField.isFirstResponder {
           validatePassword(passwordTextField.text ?? "")
        }
    }
Jardena answered 9/1, 2019 at 18:47 Comment(0)
A
1

I solve this problem by inheriting UITextField and determining whether the subview contains UIKBASPCoverView.

 open class TextField: UITextField {
     private var hasAutomaticStrongPassword: Bool = false {
         didSet {
             if oldValue && !hasAutomaticStrongPassword {
                 // After user selecting "Choose My Own Password"
                 // Manually triggering changes
                 sendActions(for: .editingChanged)
                 _ = delegate?.textField?(self, shouldChangeCharactersIn: NSRange(location: 0, length: 0), replacementString: "")
             }
         }
     }
      
     open override func layoutSubviews() {
         super.layoutSubviews()
            
         hasAutomaticStrongPassword = self.subviews.first { subview in
              NSStringFromClass(subview.classForCoder) == "UIKBASPCoverView"
         } != nil
     }
 }
Aforementioned answered 23/5, 2023 at 9:19 Comment(0)
A
0

I'm not to familiar with the delegate for "Use String password" but if the user is choosing their own password you need to check what the user types in the password textField and verify that it meets your criteria.

To do that you need to check what's typed into the password textField using it's target .editingChanged. As the user types whatever goes inside of it will either enable or disable the button. Look at step #3 and step #4. Step 4 is what will toggle and decide to wither enable or disable the button.

Another thing to take note of is that when the vc first appears the button should be disabled and then once the password textField data is valid then enable then button. Step #1 and Step #4

For example in my app if a user enters in all blank spaces inside the password textField the signUp button will be disabled but if they enter in valid data the the button will be enabled.

UPDATE I added in step 3A and 3B in viewDidLoad afterwards because as @DawsonToth correctly pointed out in the comments if the user chooses a strong password the next button will be enabled. But if they then decided to pick Choose My Own Password the passwordTextField will clear but the next button will still be enabled. I didn't account for that.

You need to add a KVO observer to the passwordTextField's "text" keypath so that every time the passwordTextField's text changes handleTextInputChanged() will get called thus disabling the next button once the text is cleared.

// 1. create a signUp button and make sure it is DISABLED and the color is .lightGray to signify that. In step 4 if the data inside the passwordTextField is valid I enable then button and change the color to blue
let signUpButton: UIButton = {
    let button = UIButton(type: .system)
    button.isEnabled = false // ** this must be set as false otherwise the user can always press the button **
    button.setTitle("SignUp", for: .normal)
    button.setTitleColor(UIColor.white, for: .normal)
    button.backgroundColor = UIColor.lightGray
    button.addTarget(self, action: #selector(signUpButtonTapped), for: .touchUpInside)
    return button
}()

// 2. create the password textField and set its delegate in viewDidLoad. For eg self.passwordTextField.delegate = self
let passwordTextField: UITextField = {
    let textField = UITextField()
    textField.placeholder = "Enter Password"
    textField.autocapitalizationType = .none
    textField.returnKeyType = .done
    textField.isSecureTextEntry = true

    // 3. add the target for .editingChanged to check the textField
    textField.addTarget(self, action: #selector(handleTextInputChanged), for: .editingChanged)
    return textField
}()

override func viewDidLoad() {
    super.viewDidLoad()

    passwordTextField.delegate = self // if this is programmatic make sure to add UITextFieldDelegate after the class name

    // 3A. Add a KVO observer to the passwordTextField's "text" keypath
    passwordTextField.addObserver(self, forKeyPath: "text", options: [.old, .new], context: nil)
}

// 3B. call the observer
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "text" {
        handleTextInputChanged()
    }
}
// 4. this checks what's typed into the password textField from step 3
@objc fileprivate func handleTextInputChanged() {

    let isFormValid = !isPasswordTextFieldIsEmpty() // if the textField ISN'T empty then the form is valid

    if isFormValid {

        signUpButton.isEnabled = true
        signUpButton.backgroundColor = .blue
    } else {

       signUpButton.isEnabled = false
       signUpButton.backgroundColor = .lightGray
    }
}

// 5. create a function to check to see if the password textField is empty
func isPasswordTextFieldIsEmpty() -> Bool {

    // 6. this checks for blank space
    let whiteSpace = CharacterSet.whitespaces

    // 7. if the passwordTextField has all blank spaces in it or is empty then return true
    if passwordTextField.text!.trimmingCharacters(in: whitespace) == "" || passwordTextField.text!.isEmpty {
        return true
    }
    return false // if it has valid characters in it then return false
}

// 8. target method from step 1
@objc func signUpButtonTapped() {
    // run firebase code
}
Anaphora answered 2/12, 2018 at 13:36 Comment(9)
Down voted because it doesn't appear that you thoroughly read the question. The OP is already doing what you suggest. Their problem is that in a very specific case the delegate is not called. The other answer addresses the problem.Loralorain
@DawsonToth apparently you thoroughly didn’t read the answer. The very first thing I said was “I’m not to familiar with the delegate...” what I gave was an alternative to the op. If you look at the date the question was asked vs the date I answered it vs the date the other answer was published you’ll notice over an entire month went by with no one answering the question. You down voted the answer because I gave an alternative when one didn’t exist. It’s counterproductive to helping people out.Anaphora
I did read it, and I do understand. It's nothing personal. The timeline of how it went down makes perfect sense to me. For people reading the Q&A, however, the current state is all that matters, not how we got there. Your answer doesn't address the core issue around the strong password not firing change events. Your answer will exhibit the same bug the original question wants to address. Thus, your answer isn't complete, will lead users astray, and does not adequately answer the question. That's why I downvoted it, to make sure that people reading will be more likely to use the other answer.Loralorain
I've up voted some of your other questions (they deserve it anyway) to counteract the reputation decrease from my down vote. Not totally by the community guidelines, but I suspect your disapproval stems from the reputation loss, not the actual validity of my assertions. I do hope you know I'm not trolling, and I'm trying to come out the other end with the best result for the community...Loralorain
@DawsonToth I appreciate the upvotes but I still think your making a mistake with this one because the op said “This works great when enabling the strong password. But I don’t seem to get a delegate callback when a user might tap Choose My Own Password”. The core issue isn’t the strong password not firing because he says it works great, his question is about Choose My Own Password and the logic used to enable and disable the button. My answer gives an alternative for that. Read his question one my time then look at my answer.Anaphora
@DawsonToth 2 points downvote doesn’t make a difference but initially it did look like you was trolling so that’s why I responded like that. But on another note one thing I’ve noticed with stackoverflow is sometimes questions get zero responses and sometimes people have the same exact problem as the op and they need anything that will work. If the answer’s code is problematic then yes it deserves a downvote. But if it works as an alternative to a question with no answer then it will still help someone along with their project. I guess that’s up for debate. Anyhow enjoy your day, cheers!Anaphora
Agreed. Your code will exhibit the bug, here's how. passwordTextField will receive focus from the user. iOS will fill in a strong password, and handleTextInputChanged will be invoked. The next button will be enabled. The user then taps "Choose My Own Password" (part of the OS controlled inputView). iOS will clear out the passwordTextField, but will NOT call handleTextInputChanged (a bug in the OS). The next button will be enabled still, since it was valid the last time handleTextInputChanged was called. The solution is to also call this method when viewDidLayoutSubviews is called. Make sense?Loralorain
@DawsonToth You are 100% correct, I forgot to account for that in my answer. I would do it differently to fix the problem. I would add a KVO on the password textfield’s keypath == “text” and then invoke handleTextInputChanged so that once the textfield’s text is cleared it should fire. I updated the answer and acknowledged you pointing out my error. Thanks for pointing that out to me 😊Anaphora
@DawsonToth you are also correct that using viewDidLayoutSubviews would also work. Another alternative would be to clear the password textfield then call handleTexInputChanged once the Choose My Own Password button is pressed. Like anything with iOS there are multiple ways to achieve something. Cheers!Anaphora
P
0

I ran into the same issue. What I did was simply to count the subviews of the textfield.

if textField.subviews.count == 3 { } // Has suggested password

I do this check in

  @objc func textFieldDidChange(_ textField: UITextField)
Portauprince answered 6/2, 2020 at 13:53 Comment(0)
S
-1

I faced the same problem.

I hopped over it by listening to the onEndEditing event in order to update the state.

<Input value={this.state.val} 
  onChangeText={(val) => this.setState({val})} 
  secureTextEntry={true} 
  onEndEditing={(e)=>{this.setState({val : e.nativeEvent.text)}}/>
Synodic answered 14/5, 2020 at 0:2 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.