今のところ、iOS 11が提供する新しいオプション、つまりアプリでパスワードを提案することをオプトアウトしたいと思います。 iOS 11でアプリを実行すると、キーボードの上部にオートフィルオプションが表示され、ユーザー名とパスワードのテキストフィールドも表示されません。
だから、私の質問は、キーボードのキーがまったく表示されず、全体的な動作がiOS 11以前と同じになるように、新しいパスワード自動入力機能をすべて無効にするにはどうすればよいですか?
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("")
を割り当てます。そうしないと、機能せず、オートフィルアクセサリビューが表示されます。
iOS 12は、isSecureTextEntry
プロパティだけでなくtextContentType
プロパティによってもパスワードtextFieldsを認識するようですので、両方を設定しない限り、このアクセサリビューを非表示にすることは実際には不可能ですtextContentTypeを何も設定せず、secureEntry機能を削除して(アプリのセキュリティ欠陥を引き起こします)、iOS 12がtextFieldをパスワードtextFieldとして認識し、この迷惑なアクセサリビューを表示できなくなります。
私の場合、アクセサリはバグを引き起こし、タップするとアプリが応答しなくなりました(アプリのレビュープロセスでアプリも拒否されました)。そのため、この機能を削除する必要がありました。私はこのセキュリティ機能を放棄したくなかったので、自分で物事を解決しなければなりませんでした。
その目的は、secureEntry機能を削除することですが、手動で追加することです。うまくいきました:
そのようにすることができます:
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
貼り付けられた値もキャッチし、前に追加するのを忘れていました。
このようにUITextContentTypeの拡張子を追加できます
extension UITextContentType {
public static let unspecified = UITextContentType("unspecified")
}
その後、あなたはそれを使用することができます
if #available(iOS 10.0, *) {
passwordField.textContentType = .unspecified
}
ユーザー名でもパスワードでもないコンテンツタイプを指定すると、この機能を無効にできます。たとえば、ユーザーがメールアドレスを入力する必要がある場合は、次を使用できます。
usernameTextField?.textContentType = .emailAddress
Ios11の非常にシンプルなアプローチが私にとってはうまくいきました。 iboutletがusernametextfieldとpasswordtextfieldであるとします。両方のアウトレットを保持するViewControllerのviewDidLoad()関数では、次のコードを使用します
usernametextfield.textContentType = UITextContentType("")
passwordtextfield.textContentType = UITextContentType("")
この後、テキストフィールドをタップしても、オートフィルアクセサリオプションは表示されません。
Objective-C
if (@available(iOS 10, *)){
self.tfEmail.textContentType = @"";
self.tfPassword.textContentType = @"";
}
これは私のために働いた。
自動入力はデフォルトでユーザーに対して有効になっています。 iOSはすべてのパスワードをキーチェーンに保存し、アプリのキーボードで使用できるようにします。 UITextView
およびUITextField
は、オートフィルパスワードとして自動的に考慮されます。ユーザー名でもパスワードでもないコンテンツタイプを指定することで無効にできますが、コンテンツタイプ情報がすでにキーチェーンに保存されている場合は、クイックバーに表示されます。空のUITextContentType
型を割り当てると、クイックバーが表示されなくなります。
例:
if #available(iOS 10.0, *) {
self.textField.textContentType = UITextContentType("")
} else {
// Fallback on earlier versions
}
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)
これはiOS 12および10で機能しました。
if (@available(iOS 10, *)) {
passwordTextField.textContentType = UITextContentTypeStreetAddressLine2;
}
if (@available(iOS 12, *)) {
passwordTextField.textContentType = UITextContentTypeOneTimeCode;
}
self.passwordTextField.autocorrectionType = NO;
うまくいかないようです、キーチェーンのサインはまだそこにあり、
self.passwordTextField.textContentType = UITextContentTypeName;
上記のコードは機能しますが、ユーザーがApple IDアカウントを設定すると、Apple idの名前がキーボードに表示されます。autocorrectionTypeを設定して無効にすることはできません。いいえ、Appleがまだこのオートフィル機能を改良しているかどうかはわかりませんが、現時点ではかなりバグがあります。
ダミーのtextContentType
をパスワードテキストフィールドに割り当てることで、ユーザー名/パスワードのコンボ検出を「オフ」にすることができます。
passwordFormField.textContentType = UITextContentType("dummy")
これにより、パスワードフィールドとその前の電子メールフィールドの両方のキーシンボルがオフになり、この方法で事前定義された値のいずれかを使用せず、キーボードアクセサリビューに無関係な提案を表示しません。
ここでさまざまな回答を試して、accessoryviewを削除する可能性が高いと結論付けることができます。しかし、これにはいくつかのバグが残っています。
キーボードを消さずにinputAccessoryViewを非表示にする方法
パスワードフィールドにのみ、カスタムキーボードを実装することもできます。また、テキストフィールドの提案を無効にしようとすると、accessoryViewも非表示になると思います。
編集:Appleフォーラムには同じ質問に関する回答はまだありません。また、私は公式のuitextfieldドキュメントでこれに関する何かを見つけることができませんでした。
私が言えることから、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)
}
}
建設的な批判を歓迎します!
@Gal Shahar Answerへの回答。
iOS 12isSecureTextEntry
プロパティだけでなく、textContentType
プロパティによってパスワードtextFieldsを認識します。
自動入力の提案をバイパスする方法。
isSecureTextEntry
プロパティをfalseに設定します。self.passwordTextField.secureTextEntry = NO;
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」はフィールドを空にします。
フォーム内のすべての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
}
}