web-dev-qa-db-fra.com

iOS 11 désactiver l'option d'affichage d'accessoire de remplissage automatique de mot de passe?

À partir de maintenant, j'aimerais désactiver la nouvelle option offerte par iOS 11, à savoir suggérer des mots de passe dans l'application. Lorsque j'exécute l'application sur iOS 11, l'option de remplissage automatique s'affiche en haut du clavier et mon champ de nom d'utilisateur et de mot de passe n'apparaît même pas.

Ma question est donc la suivante: comment désactiver la nouvelle fonctionnalité de saisie automatique du mot de passe de manière à ce que la touche du clavier ne soit pas affichée et que le comportement général soit identique à celui d’avant iOS 11?

enter image description here

67
zumzum

iOS 11 & 12 & 13 - Swift 4.2 & 5 (Mise à jour):

        if #available(iOS 12, *) {
            // iOS 12 & 13: Not the best solution, but it works.
            passwordTextField.textContentType = .oneTimeCode
        } else {
            // iOS 11: Disables the autofill accessory view. 
            // For more information see the explanation below.
            emailTextField.textContentType = .init(rawValue: "")
            passwordTextField.textContentType = .init(rawValue: "")
        }

iOS 11 explication:

Assurez-vous de configurer tous vos objets UITextField comme ceci.

Si vous avez par exemple un objet UITextField dans lequel l'utilisateur doit entrer son adresse électronique et un autre dans lequel l'utilisateur doit saisir son mot de passe, attribuez UITextContentType("") à leur propriété textContentType. Sinon, cela ne fonctionnera pas et la vue des accessoires autoFill sera toujours affichée.

57
Baran

iOS 12 semble reconnaître le mot de passe textFields également par la propriété isSecureTextEntry et pas seulement par la propriété textContentType, faisant ainsi disparaître cette vue accessoire n’est vraiment possible que si vous définissez à la fois textContentType sur none et supprimez la fonctionnalité secureEntry (et créez une faille de sécurité dans votre application) qui empêche ensuite iOS 12 de reconnaître le textField en tant que mot de passe textField et de montrer cette vue accessoire gênante.

Dans mon cas, l'accessoire a provoqué un bogue qui rendait mon application inefficace lorsque je la tapotais (ce qui a également entraîné le rejet de mon application dans le processus de révision de l'application). J'ai donc dû supprimer cette fonctionnalité. Je ne voulais pas renoncer à cette fonctionnalité de sécurité et j'ai donc dû résoudre les problèmes moi-même.

L'idée est de supprimer la fonctionnalité secureEntry mais de l'ajouter manuellement. Cela a fonctionné:

enter image description here


Cela peut être fait comme ça:

Swift 4 way:

Tout d’abord, comme indiqué ici, réglez textContentType sur rien:

if #available(iOS 10.0, *) {
    passwordText.textContentType = UITextContentType("")
    emailText.textContentType = UITextContentType("")
}

Ensuite, déclarez une variable String qui contiendra plus tard notre contenu réel textField:

var passwordValue = ""

Ajoutez une cible à la propriété passwordTextField, qui sera appelée chaque fois que le contenu de textField sera modifié:

passwordText.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)

Maintenant, voilà ce qui fera la magie, déclarez la fonction qui gérera les remplacements de texte:

@objc func textFieldDidChange(_ textField: UITextField) {
    if textField.text!.count > 1 {
        // User did copy & paste
        if passwordValue.count == 0 { // Pasted into an empty textField
            passwordValue = String(textField.text!)
        } else { // Pasted to a non empty textField
            passwordValue += textField.text!.substring(from: passwordValue.count)
        }
    } else {
        // User did input by keypad
        if textField.text!.count > passwordValue.count { // Added chars
            passwordValue += String(textField.text!.last!)
        } else if textField.text!.count < passwordValue.count { // Removed chars
            passwordValue = String(passwordValue.dropLast())
        }
    }
    self.passwordText.text = String(repeating: "•", count: self.passwordText.text!.count)
}

Enfin, définissez autocorrectionType de textField sur .no pour supprimer le texte prédictif:

passwordText.autocorrectionType = .no

Ça y est, utilisez passwordValue pour vous connecter.

J'espère que ça va aider quelqu'un.

UPDATE

Il attrape également les valeurs collées, j'ai oublié de l'ajouter auparavant.

10
Gal Shahar

Vous pouvez ajouter une extension pour UITextContentType comme ceci

extension UITextContentType {
    public static let unspecified = UITextContentType("unspecified")
}

après cela, vous pouvez l'utiliser

if #available(iOS 10.0, *) {
    passwordField.textContentType = .unspecified
}
8
Mikhail Zinov

La fonctionnalité peut être désactivée en spécifiant un type de contenu qui n'est ni nom d'utilisateur ni mot de passe. Par exemple, si l’utilisateur doit entrer une adresse électronique, vous pouvez utiliser

usernameTextField?.textContentType = .emailAddress
8
gebirgsbärbel

Une approche très simple dans iOS 11 a fonctionné pour moi. Supposons que vos iboutlets sont usernametextfield et passwordtextfield. Dans la fonction viewDidLoad () de votre viewcontroller qui contient les deux plus anciennes, utilisez le code suivant

usernametextfield.textContentType = UITextContentType("")
passwordtextfield.textContentType = UITextContentType("")

Après cela, vous ne verrez plus l'option de remplissage automatique des accessoires lorsque vous tapez sur vos champs de texte.

6
Ammad

Objective-C

if (@available(iOS 10, *)){
    self.tfEmail.textContentType = @"";
    self.tfPassword.textContentType = @"";
}

Cela a fonctionné pour moi.

5
Matheus Domingos

Le remplissage automatique est activé par défaut pour les utilisateurs. iOS enregistre tous les mots de passe dans le trousseau et les rend disponibles au clavier dans vos applications. UITextView et UITextField automatiquement pris en compte pour le mot de passe de remplissage automatique. vous pouvez désactiver en spécifiant un type de contenu qui n'est ni nom d'utilisateur ni mot de passe, mais si les informations sur le type de contenu sont déjà stockées dans le trousseau, elles s'afficheront dans la barre de navigation rapide. il est donc préférable d’attribuer un type UITextContentType vide et il n’affiche pas de barre rapide.

Exemple:

  if #available(iOS 10.0, *) {
  self.textField.textContentType = UITextContentType("")
  } else {
  // Fallback on earlier versions
 }
3
Sahil

Cela a fonctionné pour iOS 12 et 10:

if (@available(iOS 10, *)) {
    passwordTextField.textContentType = UITextContentTypeStreetAddressLine2;
}

if (@available(iOS 12, *)) {
    passwordTextField.textContentType = UITextContentTypeOneTimeCode;
}
3
no_fate

Version Objective C:

if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"10.0")) {
    self.passwordTextField.textContentType = @"";
    self.confirmPasswordTextField.textContentType = @"";
}

#define SYSTEM_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
2
wzbozon

en réponse à @Gal Shahar Answer.

iOS 12 reconnaît le mot de passe textFields par la propriété isSecureTextEntry et pas seulement par la propriété textContentType.

Pour contourner la suggestion de remplissage automatique.

  1. définissez la propriété isSecureTextEntry sur false.

self.passwordTextField.secureTextEntry = NO;

  1. Ajoutez une méthode de délégué UITextField et activez la propriété isSecureTextEntry.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if (textField == self.passwordTextField && !self.passwordTextField.secureTextEntry) {
        self.passwordTextField.secureTextEntry = YES;
    }

    return YES;
}

REMARQUE: - Do NOT Utilisez la méthode de délégué shouldBeginEditing UITextField qui affichera tout de même la suggestion de remplissage automatique. Do NOT Utilisez la méthode de délégué textFieldDidChange UITextField, elle supprimera automatiquement le premier caractère tel qu'il se produira une fois que le premier caractère est affiché. Et 'secureTextEntry' videra le champ.

1
ashish
self.passwordTextField.autocorrectionType = NO;

Cela ne semble pas fonctionner, le signe du trousseau est toujours là,

self.passwordTextField.textContentType = UITextContentTypeName;

Le code ci-dessus fonctionne, mais si les utilisateurs configurent leur compte Apple ID, le nom de l'ID Apple s'affiche sur le clavier. Vous ne pouvez pas le désactiver en définissant autocorrectionType. Non, je ne suis pas sûr que Apple continue d'affiner cette fonctionnalité de remplissage automatique. C'est tout à fait buggé pour le moment.

1
Roger Han

Vous pouvez "désactiver" la détection du nom d'utilisateur/mot de passe en affectant un pseudo textContentType au champ de texte du mot de passe.

passwordFormField.textContentType = UITextContentType("dummy")

Cela a désactivé le symbole de clé pour le champ de mot de passe et le champ de courrier électronique qui le précédait. Ainsi, vous n'utilisez pas l'une des valeurs prédéfinies et vous évitez d'afficher des suggestions sans relation dans la vue des accessoires du clavier.

1
Miguel Cabeça

Vous pouvez essayer différentes réponses ici qui concluent qu'il est probablement possible de supprimer accessoireview. Mais cela laisse quelques bugs.

Comment masquer inputAccessoryView sans quitter le clavier

Vous pourriez peut-être essayer d'implémenter un clavier personnalisé, uniquement pour les champs de mot de passe. Essayez également de désactiver les suggestions pour votre champ de texte, je pense que cela masque également l’accessoire.

EDIT: Pas de réponse sur Apple forums sur la même question pour le moment. Aussi, je n'ai rien trouvé à ce sujet dans la documentation officielle d'uitextfield.

https://forums.developer.Apple.com/thread/82442

1
Rishabh

D'après ce que je peux dire, la réponse de Bem ne fonctionne pas dans iOS 12 et la réponse de Gal Shahar ne prend pas en compte certains cas Edge (par exemple, si un utilisateur supprime plusieurs caractères à la fois). J'ai travaillé autour de cela en utilisant un IBAction, éliminant ainsi la nécessité de vérifier la version iOS complètement. Je ne suis qu'un débutant, alors ce n'est peut-être pas la "meilleure" réponse, ni la plus efficace, mais cela me semblait le plus logique:

Commencez par décocher "Entrée de texte sécurisée" dans le Storyboard ou réglez-le sur "faux"/"NON" via le code de votre mot de passe UITextField. Cela empêchera iOS d'essayer de se remplir automatiquement.

Ensuite, liez votre mot de passe UITextField à un IBAction. Le mien est appelé sur:

  • L'édition a commencé
  • L'édition a changé
  • L'édition s'est terminée

La fonction IBAction que j'ai écrite détermine les différences entre le mot de passe de départ d'un utilisateur et ce qui a été saisi dans le mot de passe UITextField et crée un nouveau mot de passe en fonction de ces informations:

class Login: UIViewController {
    var password = ""

    override func viewDidLoad() { super.viewDidLoad() }

    @IBAction func editPasswordField(_ sender: UITextField) {
        var input = Array(sender.text ?? "")
        var oldPassword = Array(password)
        var newPassword = Array("")

        //if character(s) are simply deleted from "passwordField" (not replaced or added to), "cursorPosition" is used to determine which corresponding character(s) need to also be removed from "oldPassword"
        //this is indicated by "input" comprising of only "•" (bullets) and being shorter in length than "oldPassword"
        var onlyBullets = true
        for char in input { if char != "•" { onlyBullets = false } }
        if onlyBullets && input.count < oldPassword.count {
            if let selectedRange = sender.selectedTextRange {
                let cursorPosition = sender.offset(from: sender.beginningOfDocument, to: selectedRange.start)
                let prefix = String(oldPassword.prefix(cursorPosition))
                let suffix = String(oldPassword.suffix(input.count - cursorPosition))
                input = Array(prefix + suffix)
            } else { input = Array("") }
        }

        //if no changes were made via input, input would comprise solely of a number of bullets equal to the length of "oldPassword"
        //therefore, the number of changes made to "oldPassword" via "input" can be measured with "bulletDifference" by calculating the number of characters in "input" that are NOT bullets
        var bulletDifference = oldPassword.count
        for char in input { if char == "•" { bulletDifference -= 1 } }

        //the only way "bulletDifference" can be less than 0 is if a user copy-pasted a bullet into "input", which cannot be allowed because it breaks this function
        //if a user pastes bullet(s) into "input", "input" is deleted
        //an Edge case not accounted for is pasting a mix of characters and bullets (i.e. "ex•mple") when "oldPassword.count" exceeds the number of bullets in the mixed input, but this does not cause crashes and therefore is not worth preventing
        if bulletDifference < 0 {
            bulletDifference = oldPassword.count
            input = Array("")
        }

        //"bulletDifference" is used to remove every character from "oldPassword" that corresponds with a character in "input" that has been changed
        //a changed character in "input" is indicated by the fact that it is not a bullet
        //once "bulletDifference" equals the number of bullets deleted, this loop ends
        var bulletsDeleted = 0
        for i in 0..<input.count {
            if bulletsDeleted == bulletDifference { break }
            if input[i] != "•" {
                oldPassword.remove(at: i - bulletsDeleted)
                bulletsDeleted += 1
            }
        }

        //what remains of "oldPassword" is used to substitute bullets in "input" for appropriate characters to create "newPassword"
        //for example, if "oldPassword" is "AcbDE" and "input" is "•bc••", then "oldPassword" will get truncated to "ADE" and "newPassword" will equal "A" + "bc" + "DE", or "AbcDE"
        var i = 0
        for char in input {
            if char == "•" {
                newPassword.append(oldPassword[i])
                i += 1
            } else { newPassword.append(char) }
        }
        password = String(newPassword)

        //"passwordField.text" is then converted into a string of bullets equal to the length of the new password to ensure password security in the UI
        sender.text = String(repeating: "•", count: password.count)
    }
}

La critique constructive est appréciée!

1
Escargot

Je pense que définir tout UITextField textContentType dans le formulaire sur UITextContentType("") ou .oneTimeCode n'est pas une solution propre. Activer/Désactiver isSecureTextEntry continue de vous poser le même problème.

La réponse de @Gal Shahar est Nice mais elle n'est toujours pas parfaite. Le caractère masqué n'est pas identique au caractère masqué utilisé dans le texte d'entrée sécurisé d'Apple. Il doit utiliser le caractère Unicode 'BLACK CIRCLE' (U + 25CF) https://www.fileformat.info/info/unicode/char/25cf/index.htm

En outre, il ne gère pas le mouvement du curseur. Cela changera la position du curseur à la fin du texte lors de l'insertion de texte au milieu. Cela vous donnera une mauvaise valeur lors de la sélection et du remplacement de texte.

Lorsque vous décidez d'utiliser isSecureEntryText personnalisé pour éviter le mot de passe de remplissage automatique, voici le code:

Swift 5 (version simple)

@IBOutlet weak var passwordTextField: UITextField!

var maskedPasswordChar: String = "●"
var passwordText: String = ""
var isSecureTextEntry: Bool = true {
    didSet {
        let selectedTextRange = passwordTextField.selectedTextRange
        passwordTextField.text = isSecureTextEntry ? String(repeating: maskedPasswordChar, count: passwordText.count) : passwordText
        passwordTextField.selectedTextRange = selectedTextRange
    }
}

//this is UITextFieldDelegate
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    if textField == passwordTextField {
        //update password string
        if let swiftRange = Range(range, in: passwordText) {
            passwordText = passwordText.replacingCharacters(in: swiftRange, with: string)
        } else {
            passwordText = string
        }

        //replace textField text with masked password char
        textField.text =  isSecureTextEntry ? String(repeating: maskedPasswordChar, count: passwordText.count) : passwordText

        //handle cursor movement
        if let newPosition = textField.position(from: textField.beginningOfDocument, offset: range.location + string.utf16.count) {
            textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
        }
        return false
    }
    return true
}

Swift 5 (version COMPLETE avec sécurisation de la dernière animation de caractère)

private struct Constants {
    static let SecuringLastCharPasswordDelay = 1.5
}

@IBOutlet weak var passwordTextField: UITextField!

private var secureTextAnimationQueue: [String] = []

var maskedPasswordChar: String = "●"
var passwordText: String = ""
var isSecureTextEntry: Bool = true {
    didSet {
        secureTextAnimationQueue.removeAll()
        let selectedTextRange = passwordTextField.selectedTextRange
        passwordTextField.text = isSecureTextEntry ? String(repeating: maskedPasswordChar, count: passwordText.count) : passwordText
        passwordTextField.selectedTextRange = selectedTextRange
    }
}

//this is UITextFieldDelegate
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    if textField == passwordTextField {
        //update password string
        if let swiftRange = Range(range, in: passwordText) {
            passwordText = passwordText.replacingCharacters(in: swiftRange, with: string)
        } else {
            passwordText = string
        }

        //replace textField text with masked password char
        updateTextFieldString(textField, shouldChangeCharactersIn: range, replacementString: string)

        //handle cursor movement
        if let newPosition = textField.position(from: textField.beginningOfDocument, offset: range.location + string.utf16.count) {
            textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
        }
        return false
    }
    return true
}

private func updateTextFieldString(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) {
    if isSecureTextEntry {
        if string.count == .one, let text = textField.text {
            let maskedText = String(repeating: maskedPasswordChar, count: text.count)

            var newMaskedText = String()
            if let swiftRange = Range(range, in: maskedText) {
                newMaskedText = maskedText.replacingCharacters(in: swiftRange, with: string)
            } else {
                newMaskedText = text + maskedText
            }

            textField.text = newMaskedText
            secureTextAnimationQueue.append(string)
            asyncWorker.asyncAfter(deadline: .now() + Constants.SecuringLastCharPasswordDelay) { [weak self] in
                self?.securingLastPasswordChar()
            }
        } else {
            secureTextAnimationQueue.removeAll()
            textField.text = String(repeating: maskedPasswordChar, count: passwordText.count)
        }
    } else {
        textField.text = passwordText
    }
}

private func securingLastPasswordChar() {
    guard secureTextAnimationQueue.count > .zero, isSecureTextEntry else { return }
    secureTextAnimationQueue.removeFirst()
    if secureTextAnimationQueue.count == .zero {
        let selectedTextRange = passwordTextField.selectedTextRange
        passwordTextField.text = String(repeating: maskedPasswordChar, count: passwordText.count)
        passwordTextField.selectedTextRange = selectedTextRange
    }
}
0
Sandy Akbar