UITextField con entrada segura, siempre se borra antes de editar

Tengo un problema extraño en el que mi UITextField que contiene una entrada segura siempre se borra cuando bash editarlo. Agregué 3 caracteres al campo, voy a otro campo y vuelvo, el cursor está en la posición del 4to carácter, pero cuando trato de agregar otro personaje, el nuevo personaje borra todo el texto en el campo. Tengo ‘Limpia cuando comienza la edición’ sin marcar en la punta. Entonces, ¿cuál sería el problema? Si elimino la propiedad de entrada segura, todo está funcionando bien, entonces, ¿esta es la propiedad de los campos de texto de entrada segura? ¿Hay alguna forma de prevenir este comportamiento?

Conjunto,

textField.clearsOnBeginEditing = NO; 

Nota: Esto no funcionará si secureTextEntry = YES . Parece que, de forma predeterminada, iOS borra el texto de los campos de texto de entrada segura antes de editar, sin importar que clearOnBeginEditing sea ​​SÍ o NO.

Si no desea que el campo se borre, incluso cuando secureTextEntry = YES, use:

 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string]; textField.text = updatedString; return NO; } 

Me encontré con un problema similar al agregar mostrar / ocultar la funcionalidad de texto de la contraseña a una vista de registro.

La respuesta de @ Eric funciona pero tuve dos problemas.

  1. Como señaló @malex, cualquier cambio de texto en el medio colocará el transporte al final del texto.
  2. Estoy usando rac_textSignal de ReactiveCocoa y cambiar el texto directamente no dispararía la señal.

Mi código final

 - (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 además de @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; } 

Si estás usando Swift 3 , dale una oportunidad a esta subclase.

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

¿Por qué funciona esto?

TL; DR: si está editando el campo mientras isSecureTextEntry , asegúrese de llamar a becomeFirstResponder .


Alternar el valor de isSecureTextEntry funciona bien hasta que el usuario edite el campo de texto; el campo de texto se borra antes de colocar el nuevo carácter (s). Este borrado tentativo parece ocurrir durante la llamada a UITextField de UITextField . Si esta llamada se combina con el truco deleteBackward / insertText (como se demuestra en las respuestas de @Aleksey y @dwsolberg ), el texto de entrada se conserva, aparentemente cancelando el borrado tentativo.

Sin embargo, cuando el valor de isSecureTextEntry cambia mientras el campo de texto es el primero en responder (p. Ej., El usuario ingresa su contraseña, alterna el botón “mostrar contraseña” y continúa escribiendo), el campo de texto se reiniciará como de costumbre.

Para preservar el texto de entrada en este escenario, esta subclase desencadena becomeFirstResponder solo si el campo de texto fue el primero en responder. Este paso parece faltar en otras respuestas.

Gracias @Patrick Ridd por la corrección!

Tuve un problema similar. Tengo campos de texto de inicio de sesión (secureEntry = NO) y contraseña (secureEntry = YES) incrustados en una vista de tabla. Intenté establecer

 textField.clearsOnBeginEditing = NO; 

dentro de los dos métodos delegates relevantes (textFieldDidBeginEditing y textFieldShouldBeginEditing), que no funcionó. Después de pasar del campo de contraseña al campo de inicio de sesión, todo el campo de inicio de sesión se borrará si bash eliminar un solo carácter. Utilicé textFieldShouldChangeCharactersInRange para resolver mi problema, de la siguiente manera:

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

El rango.length == 1 devuelve verdadero cuando el usuario ingresa un retroceso, que (extrañamente) es la única vez que vería el campo despejado.

He intentado todas las soluciones aquí y allá y finalmente vine con esa anulación:

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

Desencadena el texto de UITextField y luego restaura el texto original.

La respuesta de @Thomas Verbeek me ayudó mucho:

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

Excepto que encontré un error en mi código. Después de implementar su código, si tiene texto en textField y toca en el cuadro textField, solo eliminará el primer carácter y luego insertará todo el texto nuevamente. Básicamente pegando el texto de nuevo.

Para remediar esto, reemplacé el deleteBackward() con un self.text?.removeAll() y funcionó como un amuleto.

No hubiera llegado tan lejos sin la solución original de Thomas, ¡así que gracias Thomas!

Basado en la solución de Aleksey, estoy usando esta subclase.

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

Swift 3 / Swift 4

 yourtextfield.clearsOnInsertion = false yourtextfield.clearsOnBeginEditing = false 

Nota: Esto no funcionará si secureTextEntry = YES. Parece que, de forma predeterminada, iOS borra el texto de los campos de texto de entrada segura antes de editar, sin importar que clearOnBeginEditing sea SÍ o NO.

Manera fácil de usar la clase y su trabajo 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 } } 

Si desea utilizar secureTextEntry = YES y un comportamiento visual adecuado para el transporte, necesita esto:

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

Después de jugar con la solución de @malex, llegué a esta versión Swift :

1) No olvides convertir tu controlador de vista en UITextFieldDelegate:

 class LoginViewController: UIViewController, UITextFieldDelegate { ... } override func viewDidLoad() { super.viewDidLoad() textfieldPassword.delegate = self } 

2) usa esto:

 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 } 

… y si está haciendo esto para la funcionalidad “mostrar / ocultar contraseña”, lo más probable es que necesite guardar y restaurar la posición de intercalación cuando active / desactive secureTextEntry. Así es como (hazlo dentro del método de cambio):

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

Lo resolvimos basándonos en la respuesta de dwsolberg con dos soluciones:

  • el texto de la contraseña se duplicó al tocar en un campo de contraseña ya enfocado
  • el último carácter de la contraseña se reveló al tocar en el campo de contraseña
  • deleteBackward e insertText hacen que el evento de cambio de valor sea disparado

Así que se nos ocurrió esto (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 } } 

Este es un código rápido de mi proyecto que se prueba y se ocupa de, retroceso y cambios de entrada segura falso / verdadero

// obtener la entrada del usuario y llamar a los métodos de validación

 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 } 

Me doy cuenta de que esto es un poco antiguo, pero en iOS 6 el “texto” UITextField ahora está por defecto “Atribuido” en el Interface Builder. Al cambiar esto para que sea “Normal”, que era como estaba en iOS 5, se soluciona este problema.

También publicó esta misma respuesta en la pregunta que @Craig enlazó.

tuve el mismo problema, pero obtuve la solución;

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

Mi solución (hasta que se solucione el error, supongo) es subclasificar UITextField de modo que se anexe al texto existente en lugar de borrarlo como antes (o como en iOS 6). Intenta preservar el comportamiento original si no se ejecuta en 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 

Experimenté con las respuestas de dwsolberg y fluidsonic y esto parece funcionar

 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 } 

Necesitaba ajustar la solución @ thomas-verbeek, agregando una propiedad que trata el caso cuando el usuario intenta pegar cualquier texto en el campo (el texto ha sido duplicado)

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

Utilicé @EmptyStack answer textField.clearsOnBeginEditing = NO; en mi campo de texto de passwordTextField.secureTextEntry = YES; pero no funcionó en iOS11 SDK con Xcode 9.3, así que hice el siguiente código para lograrlo. En realidad, quiero mantener el texto (en el campo de texto) si el usuario cambia entre diferentes campos.

 - (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 falso en shouldChangeCharactersInRange y lo shouldChangeCharactersInRange porque este código también funciona si el usuario hace clic en el botón Eliminar para eliminar el carácter.

Hay otra publicación de pregunta de stackoverflow: Pregunta

Al leer esa publicación, parece que necesita establecer textField.clearsOnBeginEditing = NO en el método de delegado textFieldShouldBeginEditing

Guarde el texto ingresado en una propiedad. Por ejemplo:

 NSString *_password; 

Y luego en el delegado:

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