How to detect when user used Password AutoFill on a UITextField

独自空忆成欢 提交于 2019-11-29 06:53:38

Not sure if the previous answer stopped working at some point, but I can't get it to work—I only get a single didBeginEditing call when AutoFill is used.

However, I did find a way to detect AutoFill. And keep in mind that it is possible for AutoFill to be used after some characters have already been entered, for example if the user has already typed some numbers in the phone number, then they AutoFill the full number.

For Swift 4:

var rangeReplacedWithSpaceAt: Date?

var rangeReplacedWithSpace: NSRange?

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    // To detect AutoFill, look for two quick replacements. The first replaces a range with a single space.
    // The next replaces the same range with the autofilled content.
    if string == " " {
        self.rangeReplacedWithSpace = range
        self.rangeReplacedWithSpaceAt = Date()
    } else {
        if rangeReplacedWithSpace == range,
            let rangeReplacedWithSpaceAt = self.rangeReplacedWithSpaceAt,
            Date().timeIntervalSince(rangeReplacedWithSpaceAt) < 0.1 {

            DispatchQueue.main.async {
                // Whatever you use to move forward.
                self.moveForward()
            }
        }
        self.rangeReplacedWithSpace = nil
        self.rangeReplacedWithSpaceAt = nil
    }
}

Found a solution.

When the password manager is used to autofill username + password, it will trigger didBeginEditing twice, faster than a human ever could.

So, I calculate the time between the events. If the time is extremely fast, then I assume that autofill (e.g. FaceID or TouchID) was used to enter credentials and auto-trigger whatever UI is next -- in my case, the User tapping "Sign-in".

Obviously, you have to set up the correct delegation of the UITextFields you want to monitor, but once you do that:

var biometricAutofillTime: Date!

func textFieldDidBeginEditing(_ textField: UITextField) {
    if biometricAutofillTime != nil {
        if Date().timeIntervalSince(biometricAutofillTime) < 0.1 {
            // NOTE: Need to hesitate for a very short amount of time,
            //        because, otherwise, the second UITextField (password)
            //        won't yet be populated
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { self.didTapSignin() }
        }
        biometricAutofillTime = nil
    }
    biometricAutofillTime = Date()
}

I don't think there is a better solution.

One thing I noticed is that autofill is only enabled when the text field is empty.

So if the text field went from empty to a length greater than the minimum password/username, then it is most likely autofill/paste.

I am using shouldChangeCharactersIn to detect the change in the UITextField. I'm not for sure if there is a case where text from the keyboard could be batched together before the delegate method is called.

I'd like to trigger this on behalf of the user.

If this is your primary goal, I'm doing a little different approach here.

Upon showing the login form, I first check iCloud Keychain with SecRequestSharedWebCredential. If the closure returns a credentials, which means user's intent is to login with it, then I automatically login for him/her. Otherwise, make the login text filed becomeFirstResponder().

This approach does not support third-party password manager, but most people use iCloud Keychain I believe.

I found another simple way.. Hope it will help who is looking for it.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Usually string is coming as single character when user hit the keyboard .. 
// but at AutoFill case it will come as whole string which recieved .. 
// at this moment you can replace what you currently have by what you have recieved.
// In below case I'm expecting to recieve 4 digits as OTP code .. you can change it by what you are expecting to recieve.
    if string.count == 4 {
            doWhatEverYouByAutoFillString(text: string)
            return true
        } 
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!