J'ai un problème étrange dans lequel mon UITextField qui contient une entrée sécurisée est toujours effacé lorsque je tente de le modifier. J'ai ajouté 3 caractères au champ, puis un autre champ est retourné, le curseur est en 4ème position, mais lorsque j'essaie d'ajouter un autre caractère, le texte entier du champ est effacé par le nouveau caractère. J'ai «efface lorsque le montage commence» décoché dans la plume. Alors, quel serait le problème? Si je supprime la propriété d'entrée sécurisée, tout fonctionne correctement, est-ce la propriété des champs de texte d'entrée sécurisée? Y a-t-il un moyen d'empêcher ce comportement?
Ensemble,
textField.clearsOnBeginEditing = NO;
Remarque: cela ne fonctionnera pas si secureTextEntry = YES . Il semble que, par défaut, iOS efface le texte des champs de texte de saisie sécurisée avant édition, peu importe/ clearsOnBeginEditing est YES ou NO.
Si vous ne souhaitez pas que le champ soit effacé, même lorsque secureTextEntry = YES, utilisez:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
textField.text = updatedString;
return NO;
}
J'ai rencontré un problème similaire lors de l'ajout de la fonctionnalité d'affichage du texte du mot de passe/masquage à une vue d'inscription.
Si vous utilisez Swift 3 , essayez cette sous-classe.
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
}
}
Pourquoi ça marche?
TL; DR: si vous modifiez le champ en basculant isSecureTextEntry
, assurez-vous d’appeler becomeFirstResponder
.
Changer la valeur de isSecureTextEntry
fonctionne correctement jusqu'à ce que l'utilisateur modifie le champ de texte - le champ de texte est effacé avant de placer le ou les nouveaux caractères. Cette libération provisoire semble avoir lieu pendant l'appel becomeFirstResponder
de UITextField
. Si cet appel est combiné à l'astuce deleteBackward
insertText
(comme le démontrent les réponses de _/@Aleksey et @dwsolberg), le texte saisi est conservé, annulant apparemment l'effacement provisoire.
Cependant, lorsque la valeur de isSecureTextEntry
change alors que le champ de texte est le premier répondant (par exemple, l'utilisateur tape son mot de passe, bascule un bouton "Afficher le mot de passe" dans les deux sens, puis continue à taper), le champ de texte sera réinitialisé normalement.
Pour conserver le texte d'entrée dans ce scénario, cette sous-classe déclenche becomeFirstResponder
uniquement si le champ de texte était le premier répondant. Cette étape semble manquer dans d'autres réponses.
Merci @Patrick Ridd pour la correction!
La réponse de @ Eric fonctionne, mais elle me posait deux problèmes.
Mon code 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;
}
Ajout de Swift3 par @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;
}
J'avais un problème similaire. J'ai les champs de texte de connexion (secureEntry = NO) et de mot de passe (secureEntry = YES) incorporés dans une vue de tableau. J'ai essayé de mettre
textField.clearsOnBeginEditing = NO;
dans les deux méthodes déléguées pertinentes (textFieldDidBeginEditing et textFieldShouldBeginEditing), qui ne fonctionnaient pas. Après être passé du champ mot de passe au champ de connexion, tout le champ de connexion serait effacé si je tentais de supprimer un seul caractère. J'ai utilisé le textFieldShouldChangeCharactersInRange pour résoudre mon problème, comme suit:
-(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;
}
}
La valeur range.length == 1 renvoie true lorsque l'utilisateur entre un retour arrière, ce qui est (étrangement) le seul moment où je verrais le champ effacé.
J'ai essayé toutes les solutions ici et là et je suis finalement arrivé avec cette priorité:
- (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;
}
Il déclenche l'effacement de UITextField, puis restaure le texte original.
La réponse de Thomas Verbeek m'a beaucoup aidé:
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
}
}
Sauf que j'ai trouvé un bug dans mon code avec. Après avoir implémenté son code, si vous avez du texte dans textField et que vous tapez sur la zone textField, il ne supprimera que le premier caractère, puis réinsérera tout le texte. Fondamentalement, coller à nouveau le texte.
Pour remédier à cela, j'ai remplacé la deleteBackward()
par une self.text?.removeAll()
et cela a fonctionné à merveille.
Je n'aurais pas pu aller aussi loin sans la solution originale de Thomas, alors merci Thomas!
Swift 3/Swift 4
yourtextfield.clearsOnInsertion = false
yourtextfield.clearsOnBeginEditing = false
Remarque: cela ne fonctionnera pas si secureTextEntry = YES. Il semble que, par défaut, iOS efface le texte des champs de texte de saisie sécurisée avant la modification, peu importe que clearsOnBeginEditing ait la valeur YES ou NO.
Méthode d'utilisation facile et son travail 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
}
}
Basé sur la solution d'Aleksey, j'utilise cette sous-classe.
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
}
}
Changer les caractères de remplacement dans la plage ne fonctionne pas pour moi car je fais parfois d'autres choses avec cela, et ajouter plus le rend plus susceptible d'être bogué.
C'est bien parce que ça marche à peu près parfaitement. La seule chose étrange est que le dernier caractère est à nouveau affiché lorsque vous appuyez de nouveau sur le champ. En fait, j'aime bien cela parce que cela agit comme une sorte d'espace réservé.
Après avoir joué avec la solution de @malex, je suis arrivé à cette version Swift :
1) N'oubliez pas de transformer votre contrôleur de vue en UITextFieldDelegate:
class LoginViewController: UIViewController, UITextFieldDelegate {
...
}
override func viewDidLoad() {
super.viewDidLoad()
textfieldPassword.delegate = self
}
2) utiliser ceci:
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
}
... et si vous utilisez ceci pour la fonctionnalité "afficher/masquer le mot de passe", vous devrez probablement sauvegarder et restaurer la position du curseur lorsque vous activez/désactivez secureTextEntry. Voici comment (faites-le à l'intérieur de la méthode de commutation):
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)
}
}
C'est le code Swift de mon projet qui est testé et traite les modifications de retour arrière et d'entrée sécurisée false/true
// obtient la saisie de l'utilisateur et appelle les méthodes de validation
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
}
Nous l'avons résolu en se basant sur la réponse de dwsolberg avec deux corrections:
deleteBackward
et insertText
provoquent le déclenchement de l'événement de valeur modifiéeNous avons donc proposé ceci (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
}
}
Si vous souhaitez utiliser secureTextEntry = YES et un comportement visuel correct pour le transport, vous avez besoin de ceci:
- (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;
}
Ma solution (jusqu'à ce que le bogue soit corrigé, je suppose) consiste à sous-classer UITextField de telle sorte qu'il soit ajouté au texte existant au lieu d'être effacé comme auparavant (ou comme dans iOS 6). Tente de conserver le comportement d'origine si non exécuté sur 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
Je réalise que c'est un peu vieux, mais dans iOS 6, le "texte" UITextField est maintenant par défaut "Attribué" dans Interface Builder. Le fait de changer cela en "simple", comme c'était le cas dans iOS 5, corrige ce problème.
A également posté cette même réponse dans la question que @Craig a liée.
IOS 12, Swift 4
Si vous souhaitez utiliser cette solution non seulement avec la saisie de texte sécurisée, ajoutez la vérification 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
}
}
lors de la mise à niveau de mon projet vers iOS 12.1 et que ce problème existe. Ceci est ma solution à ce sujet. Ça marche bien.
- (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;
}
J'ai expérimenté des réponses de dwsolberg et de fluidsonic et cela semble fonctionner
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
}
J'ai utilisé @EmptyStack answer textField.clearsOnBeginEditing = NO;
dans le champ de saisie passwordTextField.secureTextEntry = YES;
de mon mot de passe, mais cela n'a pas fonctionné dans le SDK iOS11 avec Xcode 9.3. En fait, je souhaite conserver du texte (dans un champ de texte) si l'utilisateur bascule entre différents champs.
- (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;
}
}
J'ai retourné false dans shouldChangeCharactersInRange
et manipulé comme je le souhaite ce code fonctionne également si l'utilisateur clique sur le bouton Supprimer pour supprimer le caractère.
j'ai eu le même problème, mais j'ai eu la solution.
-(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;
}
J'avais besoin d'ajuster la solution @ thomas-verbeek en ajoutant une propriété qui traite le cas lorsque l'utilisateur tente de coller du texte dans le champ (le texte a été dupliqué)
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
}
}