web-dev-qa-db-ja.com

iOS 11は、パスワード自動入力アクセサリビューオプションを無効にしますか?

今のところ、iOS 11が提供する新しいオプション、つまりアプリでパスワードを提案することをオプトアウトしたいと思います。 iOS 11でアプリを実行すると、キーボードの上部にオートフィルオプションが表示され、ユーザー名とパスワードのテキストフィールドも表示されません。

だから、私の質問は、キーボードのキーがまったく表示されず、全体的な動作がiOS 11以前と同じになるように、新しいパスワード自動入力機能をすべて無効にするにはどうすればよいですか?

enter image description here

62
zumzum

iOS 11および12-Swift 4.2(更新済み):

        if #available(iOS 12, *) {
            // iOS 12: 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説明:

このようにUITextFieldオブジェクトをすべて設定してください。

たとえば、ユーザーが電子メールアドレスを入力する必要があるUITextFieldオブジェクトと、ユーザーがパスワードを入力する必要がある別のオブジェクトがある場合は、textContentTypeプロパティの両方にUITextContentType("")を割り当てます。そうしないと、機能せず、オートフィルアクセサリビューが表示されます。

52
Bem

iOS 12は、isSecureTextEntryプロパティだけでなくtextContentTypeプロパティによってもパスワードtextFieldsを認識するようですので、両方を設定しない限り、このアクセサリビューを非表示にすることは実際には不可能ですtextContentTypeを何も設定せず、secureEntry機能を削除して(アプリのセキュリティ欠陥を引き起こします)、iOS 12がtextFieldをパスワードtextFieldとして認識し、この迷惑なアクセサリビューを表示できなくなります。

私の場合、アクセサリはバグを引き起こし、タップするとアプリが応答しなくなりました(アプリのレビュープロセスでアプリも拒否されました)。そのため、この機能を削除する必要がありました。私はこのセキュリティ機能を放棄したくなかったので、自分で物事を解決しなければなりませんでした。

その目的は、secureEntry機能を削除することですが、手動で追加することです。うまくいきました:

enter image description here


そのようにすることができます:

Swift 4 way:

最初に、ここで回答したように、textContentTypeを何も設定しません。

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

それより、後でtextFieldの実際のコンテンツを含むString変数を宣言します。

var passwordValue = ""

PasswordTextFieldにターゲットを追加します。これは、textFieldのコンテンツが変更されるたびに呼び出されます。

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

さて、これが魔法の役目です。テキストの置換を処理する関数を宣言します。

@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)
}

最後に、textFieldのautocorrectionType.noに設定して、予測テキストを削除します。

passwordText.autocorrectionType = .no

これで、passwordValueを使用してログインを実行できます。

それが誰かを助けることを願っています。

UPDATE

貼り付けられた値もキャッチし、前に追加するのを忘れていました。

9
Gal Shahar

このようにUITextContentTypeの拡張子を追加できます

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

その後、あなたはそれを使用することができます

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

ユーザー名でもパスワードでもないコンテンツタイプを指定すると、この機能を無効にできます。たとえば、ユーザーがメールアドレスを入力する必要がある場合は、次を使用できます。

usernameTextField?.textContentType = .emailAddress
8
gebirgsbärbel

Ios11の非常にシンプルなアプローチが私にとってはうまくいきました。 iboutletがusernametextfieldとpasswordtextfieldであるとします。両方のアウトレットを保持するViewControllerのviewDidLoad()関数では、次のコードを使用します

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

この後、テキストフィールドをタップしても、オートフィルアクセサリオプションは表示されません。

6
Ammad

Objective-C

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

これは私のために働いた。

5

自動入力はデフォルトでユーザーに対して有効になっています。 iOSはすべてのパスワードをキーチェーンに保存し、アプリのキーボードで使用できるようにします。 UITextViewおよびUITextFieldは、オートフィルパスワードとして自動的に考慮されます。ユーザー名でもパスワードでもないコンテンツタイプを指定することで無効にできますが、コンテンツタイプ情報がすでにキーチェーンに保存されている場合は、クイックバーに表示されます。空のUITextContentType型を割り当てると、クイックバーが表示されなくなります。

例:

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

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

これはiOS 12および10で機能しました。

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

if (@available(iOS 12, *)) {
    passwordTextField.textContentType = UITextContentTypeOneTimeCode;
}
2
no_fate
self.passwordTextField.autocorrectionType = NO;

うまくいかないようです、キーチェーンのサインはまだそこにあり、

self.passwordTextField.textContentType = UITextContentTypeName;

上記のコードは機能しますが、ユーザーがApple IDアカウントを設定すると、Apple idの名前がキーボードに表示されます。autocorrectionTypeを設定して無効にすることはできません。いいえ、Appleがまだこのオートフィル機能を改良しているかどうかはわかりませんが、現時点ではかなりバグがあります。

1
Roger Han

ダミーのtextContentTypeをパスワードテキストフィールドに割り当てることで、ユーザー名/パスワードのコンボ検出を「オフ」にすることができます。

passwordFormField.textContentType = UITextContentType("dummy")

これにより、パスワードフィールドとその前の電子メールフィールドの両方のキーシンボルがオフになり、この方法で事前定義された値のいずれかを使用せず、キーボードアクセサリビューに無関係な提案を表示しません。

1
Miguel Cabeça

ここでさまざまな回答を試して、accessoryviewを削除する可能性が高いと結論付けることができます。しかし、これにはいくつかのバグが残っています。

キーボードを消さずにinputAccessoryViewを非表示にする方法

パスワードフィールドにのみ、カスタムキーボードを実装することもできます。また、テキストフィールドの提案を無効にしようとすると、accessoryViewも非表示になると思います。

編集:Appleフォーラムには同じ質問に関する回答はまだありません。また、私は公式のuitextfieldドキュメントでこれに関する何かを見つけることができませんでした。

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

1
Rishabh

私が言えることから、Bemの答えはiOS 12では機能せず、Gal Shaharの答えはいくつかのエッジケースを考慮していません(たとえば、ユーザーが複数の文字を一度に削除した場合)。 IBActionを使用してこれを回避したため、iOSのバージョンを完全に確認する必要がなくなりました。私はまだ初心者なので、これは「最良の」答えでも、最も効率的なものでもないかもしれませんが、私にとって最も意味がありました。

最初に、ストーリーボードで「セキュアテキストエントリ」のチェックを外すか、パスワードUITextFieldのコードを使用して「false」/「NO」に設定します。これにより、iOSがオートフィルを試行できなくなります。

次に、パスワードUITextFieldをIBActionにリンクします。私が呼び出されます:

  • 編集が始まりました
  • 編集は変更されました
  • 編集は終了しました

私が書いたIBAction関数は、ユーザーの開始パスワードとパスワードUITextFieldに入力されたものとの違いを判断し、この情報に基づいて新しいパスワードを作成します。

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)
    }
}

建設的な批判を歓迎します!

1
Escargot

@Gal Shahar Answerへの回答。

iOS 12isSecureTextEntryプロパティだけでなく、textContentTypeプロパティによってパスワードtextFieldsを認識します。

自動入力の提案をバイパスする方法。

  1. isSecureTextEntryプロパティをfalseに設定します。

self.passwordTextField.secureTextEntry = NO;

  1. UITextFieldデリゲートメソッドを追加し、isSecureTextEntryプロパティを有効にします。
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if (textField == self.passwordTextField && !self.passwordTextField.secureTextEntry) {
        self.passwordTextField.secureTextEntry = YES;
    }

    return YES;
}

注:-実行NOTshouldBeginEditing UITextFieldデリゲートメソッドを使用すると、自動入力候補が表示されます。 Do NOTtextFieldDidChange UITextFieldデリゲートメソッドを使用します。最初の文字が表示された後に発生するため、最初の文字を自動削除します。そして、「secureTextEntry」はフィールドを空にします。

0
ashish

フォーム内のすべてのUITextField textContentTypeをUITextContentType("")または.oneTimeCodeに設定するのはクリーンなソリューションではないと思います。 isSecureTextEntryを有効/無効にしても、同じ問題が発生します。

@Gal Shaharの回答は素晴らしいですが、まだ完璧ではありません。マスクされた文字は、Appleのセキュアエントリテキストで使用されたマスクされた文字とは異なります。 Unicode文字「BLACK CIRCLE」(U + 25CF)を使用する必要があります https://www.fileformat.info/info/unicode/char/25cf/index.htm

また、カーソルの移動を処理していません。中央にテキストを挿入すると、カーソル位置がテキストの末尾に変更されます。テキストを選択して置換するときに、間違った値が与えられます。

自動入力パスワードを回避するためにカスタムisSecureEntryTextを使用することにした場合のコードは次のとおりです。

Swift 5(シンプルバージョン)

@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(最後の文字アニメーションを保護する完全版)

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