web-dev-qa-db-ja.com

セキュアエントリを備えたUITextField、編集前に常にクリアされる

安全なエントリを保持しているUITextFieldを編集しようとすると、常にクリアされるという奇妙な問題があります。 3 charactersをフィールドに追加し、別のフィールドに移動して戻ってきました。カーソルは4番目のcharacter位置にありますが、別の文字を追加しようとすると、フィールド内のテキスト全体が取得されます新しいキャラクターによってクリアされます。ペン先で[編集開始時にクリア]のチェックを外しています。では、問題は何でしょうか?セキュアエントリプロパティを削除すると、すべてが正常に機能しているので、これはセキュアエントリtextfieldsのプロパティですか?この動作を防ぐ方法はありますか?

35
Nithin

セットする、

textField.clearsOnBeginEditing = NO;

注:secureTextEntry = YESの場合、これは機能しません。デフォルトでは、iOSはセキュアエントリのテキストをクリアするようです編集前のテキストフィールド、clearsOnBeginEditingはYESまたはNOです。

47
EmptyStack

SecureTextEntry = YESの場合でも、フィールドをクリアしたくない場合は、次を使用します。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];

    textField.text = updatedString;

    return NO;
}

サインアップビューにパスワードテキストの表示/非表示機能を追加すると、同様の問題が発生しました。

47
Eric

Swiftを使用している場合、このサブクラスを試してください。

class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            self.text?.removeAll()
            insertText(text)
        }
        return success
    }

}

これはなぜ機能するのですか?

TL; DR:isSecureTextEntryを切り替えながらフィールドを編集している場合は、必ずbecomeFirstResponderを呼び出してください。


isSecureTextEntryの値の切り替えは、ユーザーがテキストフィールドを編集するまで正常に機能します。テキストフィールドは新しい文字を配置する前にクリアされます。この一時的な消去は、becomeFirstResponderUITextField呼び出し中に発生するようです。この呼び出しがdeleteBackward/insertTextトリックと組み合わされた場合(@ Alekseyおよび@ dwsolberg)、入力テキストは保持され、一時的な消去がキャンセルされたように見えます。

ただし、テキストフィールドが最初のレスポンダーである間にisSecureTextEntryの値が変更されると(たとえば、ユーザーがパスワードを入力し、「パスワードを表示」ボタンを前後に切り替え、入力を続ける)、テキストフィールドは次のようにリセットされますいつも。

このシナリオで入力テキストを保持するために、このサブクラスは、テキストフィールドが最初のレスポンダーである場合にのみbecomeFirstResponderをトリガーします。このステップは、他の回答では欠落しているようです。

@Patrick Riddが訂正してくれてありがとう!

18
Thomas Verbeek

@Ericの答えは機能しますが、2つの問題がありました。

  1. @malexが指摘したように、途中でテキストを変更すると、テキストの最後にキャリッジが配置されます。
  2. ReactiveCocoaのrac_textSignalを使用していますが、テキストを直接変更しても信号はトリガーされません。

私の最終的なコード

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    //Setting the new text.
    NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
    textField.text = updatedString;

    //Setting the cursor at the right place
    NSRange selectedRange = NSMakeRange(range.location + string.length, 0);
    UITextPosition* from = [textField positionFromPosition:textField.beginningOfDocument offset:selectedRange.location];
    UITextPosition* to = [textField positionFromPosition:from offset:selectedRange.length];
    textField.selectedTextRange = [textField textRangeFromPosition:from toPosition:to];

    //Sending an action
    [textField sendActionsForControlEvents:UIControlEventEditingChanged];

    return NO;
}

@MarsによるSwift3の追加:

  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let nsString:NSString? = textField.text as NSString?
    let updatedString = nsString?.replacingCharacters(in:range, with:string);

    textField.text = updatedString;


    //Setting the cursor at the right place
    let selectedRange = NSMakeRange(range.location + string.length, 0)
    let from = textField.position(from: textField.beginningOfDocument, offset:selectedRange.location)
    let to = textField.position(from: from!, offset:selectedRange.length)
    textField.selectedTextRange = textField.textRange(from: from!, to: to!)

    //Sending an action
    textField.sendActions(for: UIControlEvents.editingChanged)

    return false;
}
15
Ahmad Baraka

同様の問題がありました。ログイン(secureEntry = NO)およびパスワード(secureEntry = YES)のテキストフィールドがテーブルビューに埋め込まれています。設定してみた

textField.clearsOnBeginEditing = NO;

両方の関連デリゲートメソッド(textFieldDidBeginEditingおよびtextFieldShouldBeginEditing)の内部で、機能しませんでした。パスワードフィールドからログインフィールドに移動した後、単一の文字を削除しようとすると、ログインフィールド全体がクリアされます。次のように、textFieldShouldChangeCharactersInRangeを使用して問題を解決しました。

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (range.length == 1 && textField.text.length > 0) 
    {
        //reset text manually
        NSString *firstPart = [textField.text substringToIndex:range.location]; //current text minus one character
        NSString *secondPart = [textField.text substringFromIndex:range.location + 1]; //everything after cursor
        textField.text = [NSString stringWithFormat:@"%@%@", firstPart, secondPart];

        //reset cursor position, in case character was not deleted from end of 
        UITextRange *endRange = [textField selectedTextRange];
        UITextPosition *correctPosition = [textField positionFromPosition:endRange.start offset:range.location - textField.text.length];
        textField.selectedTextRange = [textField textRangeFromPosition:correctPosition toPosition:correctPosition];

        return NO;
    }
    else
    {
        return YES;
    }
}

ユーザーがバックスペースを入力すると、range.length == 1はtrueを返します。これは、(奇妙なことに)フィールドがクリアされるのを見る唯一の時間です。

5
Ampers4nd

私はあちこちですべてのソリューションを試してみましたが、最終的にそのオーバーライドを使用しました:

- (BOOL)becomeFirstResponder
{
    BOOL became = [super becomeFirstResponder];
    if (became) {
        NSString *originalText = [self text];
        //Triggers UITextField to clear text as first input
        [self deleteBackward];

        //Requires setting text via 'insertText' to fire all associated events
        [self setText:@""];
        [self insertText:originalText];
    }
    return became;
}

UITextFieldのクリアをトリガーし、元のテキストを復元します。

5
Aleksey

Swift 3/Swift 4

yourtextfield.clearsOnInsertion = false
yourtextfield.clearsOnBeginEditing = false

注:secureTextEntry = YESの場合、これは機能しません。デフォルトでは、clearsOnBeginEditingがYESでもNOでも、iOSは編集前にセキュアエントリテキストフィールドのテキストをクリアするようです。

簡単な使い方のクラスとその仕事100%

class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            self.text?.removeAll()
            insertText(text)
        }
         return success
    }

}
5
Shakeel Ahmed

@Thomas Verbeekの答えは私を大いに助けてくれました。

_class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            deleteBackward()
            insertText(text)
        }
        return success
    }

}
_

コードにバグが見つかった場合を除きます。彼のコードを実装した後、textFieldにテキストがあり、textFieldボックスをタップすると、最初の文字のみが削除され、すべてのテキストが再び挿入されます。基本的に再びテキストを貼り付けます。

これを改善するために、私はdeleteBackward()self.text?.removeAll()に置き換えました。それは魅力のように機能しました。

トーマスの元の解決策がなければ、そこまでは得られなかったでしょう。トーマスに感謝します!

5
Patrick Ridd

Alekseyのソリューションに基づいて、このサブクラスを使用しています。

class SecureNonDeleteTextField: UITextField {

    override func becomeFirstResponder() -> Bool {
        guard super.becomeFirstResponder() else { return false }
        guard self.secureTextEntry == true else { return true }
        guard let existingText = self.text else { return true }
        self.deleteBackward() // triggers a delete of all text, does NOT call delegates
        self.insertText(existingText) // does NOT call delegates
        return true
    }
}

範囲内の置換文字を変更しても機能しないことがありますが、それを使用して他のことを行っている場合があり、さらに追加するとバグが発生しやすくなります。

完璧に機能するため、これは素晴らしいことです。唯一奇妙なのは、フィールドに戻ったときに最後の文字が再び表示されることです。それが一種のプレースホルダーとして機能するので、私は実際にそれが好きです。

2
dwsolberg

@malexのソリューションで遊んだ後、私はこれに来ましたSwift version:

1)View ControllerをUITextFieldDelegateにすることを忘れないでください:

class LoginViewController: UIViewController, UITextFieldDelegate {
...
}

override func viewDidLoad() {
   super.viewDidLoad()
   textfieldPassword.delegate = self
}

2)これを使用:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    if let start: UITextPosition = textField.positionFromPosition(textField.beginningOfDocument, offset: range.location),
       let end: UITextPosition = textField.positionFromPosition(start, offset: range.length),
       let textRange: UITextRange = textField.textRangeFromPosition(start, toPosition: end) {
           textField.replaceRange(textRange, withText: string)
    }
    return false
}

...そして「パスワードの表示/非表示」機能のためにこれを行っている場合、おそらくsecureTextEntryのオン/オフを切り替えるときにキャレット位置を保存および復元する必要があります。方法は次のとおりです(切り替え方法の中で実行します)。

var startPosition: UITextPosition?
var endPosition: UITextPosition?

// Remember the place where cursor was placed before switching secureTextEntry
if let selectedRange = textfieldPassword.selectedTextRange {
   startPosition = selectedRange.start
   endPosition = selectedRange.end
}

...

// After secureTextEntry has been changed
if let start = startPosition {
   // Restoring cursor position
   textfieldPassword.selectedTextRange = textfieldPassword.textRangeFromPosition(start, toPosition: start)
   if let end = endPosition {
       // Restoring selection (if there was any)
       textfieldPassword.selectedTextRange = textfield_password.textRangeFromPosition(start, toPosition: end)
       }
}
1
Vitalii

これはSwift私のプロジェクトからのコードがテストされ、対処されている、バックスペースおよびセキュアエントリの変更false/true

//ユーザー入力を取得し、検証メソッドを呼び出します

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

    if (textField == passwordTextFields) {

        let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)

        // prevent backspace clearing the password
        if (range.location > 0 && range.length == 1 && string.characters.count == 0) {
            // iOS is trying to delete the entire string
            textField.text = newString
            choosPaswwordPresenter.validatePasword(text: newString as String)

            return false
        }

        // prevent typing clearing the pass
        if range.location == textField.text?.characters.count {
            textField.text = newString
            choosPaswwordPresenter.validatePasword(text: newString as String)

            return false
        }

        choosPaswwordPresenter.validatePasword(text: newString as String)
    }

    return true
}
1
ovidiur

IOS 12、Swift 4

  • テキストフィールドが再びファーストレスポンダーになったときに最後の文字を表示しません。
  • テキストフィールドを繰り返しタッチしても、既存のテキストは複製されません。

セキュアテキストエントリだけでなくこのソリューションを使用する場合は、isSecureTextEntryチェックを追加します。

class PasswordTextField: UITextField {
    override func becomeFirstResponder() -> Bool {
        let wasFirstResponder = isFirstResponder
        let success = super.becomeFirstResponder()
        if !wasFirstResponder, let text = self.text {
            insertText("\(text)+")
            deleteBackward()
        }
        return success
    }
}
1
nowhale

UITextFieldのサブクラスを作成し、以下の2つのメソッドをオーバーライドします。

-(void)setSecureTextEntry:(BOOL)secureTextEntry {
    [super setSecureTextEntry:secureTextEntry];

    if ([self isFirstResponder]) {
        [self becomeFirstResponder];
    }
}

-(BOOL)becomeFirstResponder {
    BOOL became = [super becomeFirstResponder];
    if (became) {
        NSString *originalText = [self text];
        //Triggers UITextField to clear text as first input
        [self deleteBackward];

        //Requires setting text via 'insertText' to fire all associated events
        [self setText:@""];
        [self insertText:originalText];
    }
    return became;
}

このクラス名をテキストフィールドクラス名として使用します。

1
Milan Agarwal

dwsolberg's answer に基づいて解決し、2つの修正を加えました。

  • すでにフォーカスされているパスワードフィールドをタップすると、パスワードテキストが複製された
  • パスワードフィールドをタップすると、パスワードの最後の文字が明らかになりました
  • deleteBackwardおよびinsertTextにより、値変更イベントが発生します

そこで、私たちはこれを思いつきました(Swift 2.3):

class PasswordTextField: UITextField {

    override func becomeFirstResponder() -> Bool {
        guard !isFirstResponder() else {
            return true
        }
        guard super.becomeFirstResponder() else {
            return false
        }
        guard secureTextEntry, let text = self.text where !text.isEmpty else {
            return true
        }

        self.text = ""
        self.text = text

        // make sure that last character is not revealed
        secureTextEntry = false
        secureTextEntry = true

        return true
    }
}
1
fluidsonic

SecureTextEntry = YESおよびキャリッジの適切な視覚的動作を使用する場合、これが必要です。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
{

    if (!string.length) {
        UITextPosition *start = [self positionFromPosition:self.beginningOfDocument offset:range.location];
        UITextPosition *end = [self positionFromPosition:start offset:range.length];
        UITextRange *textRange = [self textRangeFromPosition:start toPosition:end];
        [self replaceRange:textRange withText:string];
    }
    else {
       [self replaceRange:self.selectedTextRange withText:string];
    }

    return NO;
}
1
malex

私の解決策(バグが修正されるまで)は、以前(またはiOS 6)のようにクリアするのではなく、既存のテキストに追加するようにUITextFieldをサブクラス化することです。 iOS 7で実行されていない場合、元の動作を維持しようとします。

@interface ZTTextField : UITextField {
    BOOL _keyboardJustChanged;
}
@property (nonatomic) BOOL keyboardJustChanged;
@end

@implementation ZTTextField
@synthesize keyboardJustChanged = _keyboardJustChanged;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        _keyboardJustChanged = NO;
    }
    return self;
}

- (void)insertText:(NSString *)text {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
    if (self.keyboardJustChanged == YES) {
        BOOL isIOS7 = NO;
        if ([[UIApplication sharedApplication] respondsToSelector:@selector(backgroundRefreshStatus)]) {
            isIOS7 = YES;
        }
        NSString *currentText = [self text];
        // only mess with editing in iOS 7 when the field is masked, wherein our problem lies
        if (isIOS7 == YES && self.secureTextEntry == YES && currentText != nil && [currentText length] > 0) {
            NSString *newText = [currentText stringByAppendingString: text];
            [super insertText: newText];
        } else {
            [super insertText:text];
        }
        // now that we've handled it, set back to NO
        self.keyboardJustChanged = NO;
    } else {
        [super insertText:text];
    }
#else
    [super insertText:text];
#endif
}

- (void)setKeyboardType:(UIKeyboardType)keyboardType {
    [super setKeyboardType:keyboardType];
    [self setKeyboardJustChanged:YES];
}

@end
0
Billy Gray

私はこれが少し古いことを認識していますが、iOS 6では、UITextFieldの「テキスト」はデフォルトでInterface Builderの「属性」になっています。これを「プレーン」に切り替えると、iOS 5のようになり、この問題が修正されます。

@Craigがリンクした質問にもこの同じ回答を投稿しました。

0
Matt S.

私のプロジェクトがiOS 12.1にアップグレードし、この問題が発生したとき。これはこれに関する私の解決策です。大丈夫です。



    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
        NSMutableString *checkString = [textField.text mutableCopy];
        [checkString replaceCharactersInRange:range withString:string];
        textField.text = checkString;
        NSRange selectedRange = NSMakeRange(range.location + string.length, 0);
        UITextPosition* from = [textField positionFromPosition:textField.beginningOfDocument offset:selectedRange.location];
        UITextPosition* to = [textField positionFromPosition:from offset:selectedRange.length];
        textField.selectedTextRange = [textField textRangeFromPosition:from toPosition:to];
        [textField sendActionsForControlEvents:UIControlEventEditingChanged];
        return NO;
    }

0
Ace

私はdwsolbergとfluidsonicの答えを実験しましたが、これはうまくいくようです

override func becomeFirstResponder() -> Bool {

    guard !isFirstResponder else { return true }
    guard super.becomeFirstResponder() else { return false }
    guard self.isSecureTextEntry == true else { return true }
    guard let existingText = self.text else { return true }
    self.deleteBackward() // triggers a delete of all text, does NOT call delegates
    self.insertText(existingText) // does NOT call delegates

    return true
}
0
Ivan

パスワードテキストフィールドtextField.clearsOnBeginEditing = NO;で@EmptyStackの回答passwordTextField.secureTextEntry = YES;を使用しましたが、Xcode 9.3を使用したiOS11 SDKでは機能しなかったため、次のコードを実行しました。実際、ユーザーが異なるフィールド間で切り替えた場合、テキスト(テキストフィールド内)を保持します。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (textField.tag == 2) {
        if ([string isEqualToString:@""] && textField.text.length >= 1) {
            textField.text = [textField.text substringToIndex:[textField.text length] - 1];
        } else{
            textField.text = [NSString stringWithFormat:@"%@%@",textField.text,string];
        }
        return false;
    } else {
        return true;
    }
}

shouldChangeCharactersInRangeでfalseを返し、ユーザーが文字を削除するために削除ボタンをクリックしてもこのコードが機能するように操作しました。

0
Aleem

私は同じ問題を抱えていたが、解決策を得た。

-(BOOL)textFieldShouldReturn:(UITextField *)textField
    {

        if(textField==self.m_passwordField)
        {
            text=self.m_passwordField.text;  
        }

        [textField resignFirstResponder];

        if(textField==self.m_passwordField)
        {
            self.m_passwordField.text=text;
        }
        return YES;
    }
0
Superdev

私の前の答えに感謝します。行う必要があるのは、isSecureTextEntryが設定された直後に同じ文字列オブジェクトのテキストを削除および挿入することだけであるようです。そこで、以下に拡張機能を追加しました。

extension UITextField {
    func setSecureTextEntry(_ on: Bool, clearOnBeginEditing: Bool = true)   {
        isSecureTextEntry = on

        guard on,
              !clearOnBeginEditing,
              let textCopy = text
        else { return }

        text?.removeAll()
        insertText(textCopy)
    }
}
0
redmage1993

@ thomas-verbeekソリューションを調整する必要がありました。ユーザーがフィールドにテキストを貼り付けようとした場合に対処するプロパティを追加することにより(テキストが複製されました)

class PasswordTextField: UITextField {

    private var barier = true

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
            }
        }
    }

    override func becomeFirstResponder() -> Bool {
        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text, barier {
            deleteBackward()
            insertText(text)
        }
        barier = !isSecureTextEntry
        return success
    }

}
0
olejnjak