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