UITextField с защищенной записью, всегда очищается перед редактированием

У меня странная проблема, из-за которой мой UITextField, который содержит защищенную запись, всегда очищается, когда я пытаюсь ее отредактировать. Я добавил 3 characters в поле, перешел в другое поле и вернулся, курсор находится на 4-й позиции character, но когда я пытаюсь добавить другой символ, весь текст в поле очищается новым символом. У меня "Очистить, когда редактирование начинается" снят в перо. Так в чем же проблема? Если я удаляю свойство защищенной записи, все работает нормально, поэтому это свойство Защищенной записи textfields? Есть ли способ предотвратить такое поведение?

Ответ 1

Установить

textField.clearsOnBeginEditing = NO;

Примечание. Это не будет работать, если secureTextEntry = YES. По-видимому, по умолчанию iOS очищает текст текстовых полей безопасного ввода до редактирования, независимо от того, clearsOnBeginEditing - ДА или НЕТ.

Ответ 2

Если вы не хотите, чтобы поле очищалось, даже если 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;
}

Я столкнулся с аналогичной проблемой при добавлении функции текста "показать/скрыть пароль" в виде регистрации.

Ответ 3

Если вы используете Swift 3, отпустите этот подкласс.

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 отлично работает, пока пользователь не редактирует текстовое поле - текстовое поле очищается перед размещением нового символа (ов). Эта предварительная очистка, по-видимому, происходит во время вызова becomeFirstResponder UITextField. Если этот вызов сочетается с трюком deleteBackward/insertText (как показано в ответах @Aleksey и @dwsolberg), входной текст сохраняется, по-видимому, отменяя предварительную очистку.

Однако, когда значение isSecureTextEntry изменяется, когда текстовое поле является первым ответчиком (например, пользователь вводит свой пароль, переключает кнопку "показать пароль" взад и вперед, затем продолжает печатать), текстовое поле будет reset как обычно.

Чтобы сохранить текст ввода в этом сценарии, этот подкласс запускает becomeFirstResponder только в том случае, если текстовое поле было первым ответчиком. Этот шаг, кажется, отсутствует в других ответах.

Спасибо @Patrick Ridd за исправление!

Ответ 4

@Эрик работает, но у меня было две проблемы.

  • Как отметил @malex, любое изменение текста по середине будет содержать каретку в конце текста.
  • Я использую rac_textSignal из ReactiveCocoa, и изменение текста напрямую не приведет к сигналу.

Мой последний код

- (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;
}

Добавление Swift3 by @Mars:

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

Ответ 5

@Ответ 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, он удалит только первый char, а затем снова вставляет весь текст. В основном вставка текста снова.

Чтобы исправить это, я заменил deleteBackward() на self.text?.removeAll(), и он работал как шарм.

Я бы не стал так далеко от оригинального решения Томаса, поэтому спасибо Томасу!

Ответ 6

У меня была аналогичная проблема. У меня есть логин (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, когда пользователь вводит обратное пространство, которое (как ни странно) является единственным временем, когда я увижу, что поле очищено.

Ответ 7

Я пробовал все решения здесь и там и, наконец, пришел с этим переопределением:

- (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 и затем восстанавливает исходный текст.

Ответ 8

Swift 3/Swift 4

yourtextfield.clearsOnInsertion = false
yourtextfield.clearsOnBeginEditing = false

Примечание. Это не будет работать, если secureTextEntry = YES. По-видимому, по умолчанию iOS очищает текст текстовых полей безопасного ввода перед редактированием, независимо от того, как очиститьOnBeginEditing - ДА или НЕТ.

Простой способ использования и его работа 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
    }

}

Ответ 9

Основываясь на решении Алексея, я использую этот подкласс.

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

Ответ 10

Если вы хотите использовать 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;
}

Ответ 11

После игры с решением от @malex я пришел к этой версии Swift:

1) Не забудьте сделать свой контроллер просмотра 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)
       }
}

Ответ 12

Мы решили это на основе ответа dwsolberg с двумя исправлениями:

  • текст пароля дублируется при нажатии на уже сфокусированное поле пароля
  • последний символ пароля был обнаружен при нажатии в поле пароля
  • 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
    }
}

Ответ 13

Это быстрый код из моего проекта, который протестирован и имеет дело с изменениями backspace и secure-entry 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
}

Ответ 14

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

Ответ 15

Создайте подкласс UITextField и переопределите следующие два метода:

-(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;
}

Используйте это имя класса в качестве имени класса текстового поля.

Ответ 16

Я понимаю, что это немного старо, но в iOS 6 UITextField "текст" теперь по умолчанию "Attributed" в Interface Builder. Переключение этого на "Обычное", как это было в iOS 5, устраняет эту проблему.

Также был опубликован этот же ответ в вопросе, связанный с @Craig.

Ответ 17

У меня была та же проблема, но она получила решение;

-(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;
    }

Ответ 18

Мое решение (до тех пор, пока ошибка не будет исправлена, я полагаю) является подклассом UITextField таким образом, что он добавляется к существующему тексту вместо очистки как прежде (или как в iOS 6). Попытки сохранить исходное поведение, если он не запущен на 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

Ответ 19

Я экспериментировал с ответами 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
}

Ответ 20

Мне нужно было настроить решение @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
    }

}

Ответ 21

Я использовал @EmptyStack ответ textField.clearsOnBeginEditing = NO; в текстовом поле моего пароля passwordTextField.secureTextEntry = YES;, но он не сработал в SDK iOS11 с Xcode 9.3, поэтому я сделал следующий код для достижения. На самом деле я хочу сохранить текст (в текстовом поле), если пользователь переключается между разными полями.

- (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;
    }
}

Я вернул false в shouldChangeCharactersInRange и манипулировал, поскольку я хочу, чтобы этот код также работал, если пользователь нажимает кнопку удаления, чтобы удалить символ.

Ответ 22

когда мой проект обновится до 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;
    }

Ответ 23

Спасибо за ответы до меня. Кажется, что все, что нужно сделать, это удалить и вставить текст в тот же строковый объект сразу после установки isSecureTextEntry. Поэтому я добавил расширение ниже:

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

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

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

Ответ 24

Есть еще один вопрос с вопросом о stackoverflow: Question

Чтение этого сообщения похоже на то, что вам нужно установить textField.clearsOnBeginEditing = NO в методе делегата textFieldShouldBeginEditing

Ответ 25

Сохранить текст, введенный в свойство. Например:

NSString *_password;

И затем в делегате:

textFieldDidBeginEditing:(UITextField *)textField
assign textField.text = _password;