UITextField with protected record, always cleared before editing

I have a weird problem whereby mine UITextField

, which contains a protected entry, is always cleared when I try to edit it. I added 3 characters

to the field, went to another field and came back, the cursor is at the 4th position character

, but when I try to add another character, all the text in the field is cleared with the new character. I have "Clear When Editing Begins" removed in pen. So what's the problem? If I remove the Protected Record property everything works fine, so is this the Protected Record property textfields

? Is there a way to prevent this behavior?

+38


source to share


25 replies


Install

textField.clearsOnBeginEditing = NO;

      



Note. This will not work if secureTextEntry = YES . Apparently, by default, iOS clears the text of safe input text boxes prior to editing, regardless of whether clearsOnBeginEditing is YES or NO.

+47


source


If you don't want the field to be cleared even if 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;
}

      



I ran into a similar problem when adding a show / hide password text function as a registration.

+47


source


If you are using Swift 3 then drop this subclass.

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
    }

}

      

Why does it work?

TL; DR: if you edit a field when toggling isSecureTextEntry

, make sure you call becomeFirstResponder

.


The value toggle isSecureTextEntry

works fine as long as the user is not editing the textbox - the textbox is cleared before placing the new character (s). This pre-cleanup appears to happen during the call becomeFirstResponder

UITextField

. If this call is combined with the deleteBackward

/ trick insertText

(as shown in the answers by @Aleksey and @dwsolberg), the input text is preserved, seemingly canceling the precleaning.

However, when the value isSecureTextEntry

changes when the textbox is the first responder (for example, the user enters their password, toggles the "show password" button back and forth, then continues typing), the textbox will be reset as usual.

To keep the input text in this script, this subclass becomeFirstResponder

only runs if the textbox was the first responder. This step seems to be missing from other answers.

Thanks @Patrick Ridd for the fix!

+20


source


@Erik works, but I had two problems.

  • As @malex pointed out, any change to the text in the middle will contain a caret at the end of the text.
  • I am using rac_textSignal from ReactiveCocoa and changing the text will not directly trigger a signal.

My last code

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

      

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

      

+16


source


@ Thomas Verbeek's answer helped me a lot:

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
    }

}

      

Also, I found a bug in my code. After implementing his code, if you have text in the textfield and you click on the textField, it will only remove the first char and then re-insert all the text. Basically pasting text again.

To fix this, I replaced deleteBackward()

with self.text?.removeAll()

and it worked like a charm.

I wouldn't get that far from Thomas's original solution, so thanks to Thomas!

+7


source


I had a similar problem. I have a login (secureEntry = NO) and password (secureEntry = YES) embedded in the table view. I tried to install

textField.clearsOnBeginEditing = NO;

      

inside both of the respective delegate methods (textFieldDidBeginEditing and textFieldShouldBeginEditing) that don't work. After moving from the password field to the login field, the entire input field is cleared if I try to delete one character. I used textFieldShouldChangeCharactersInRange to solve my problem like this:

-(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 returns true when the user enters backspace, which is (oddly enough) the only time I see the field cleared.

+5


source


I tried all the solutions here and there and finally came up with this override:

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

      

It launches the UITextField and then restores the original text.

+5


source


Swift 3 / Swift 4

yourtextfield.clearsOnInsertion = false
yourtextfield.clearsOnBeginEditing = false

      

Note. This will not work if secureTextEntry = YES. Apparently, by default, iOS clears the text of safe input text boxes before editing, regardless of whether the OnBeginEditing is cleared YES or NO.

Easy way to use and it works 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


source


Based on Alexey's solution, I am using this subclass.

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

Run codeHide result


Changing the character substitution in the range doesn't work for me because sometimes I do something about it and adding a larger number makes it more likely.

This is good because it works great. The only oddity is that the last character is displayed again when you step back into the field. I really like this because it acts like a kind of placeholder.

+2


source


If you want to use secureTextEntry = YES and the correct visual behavior for transport, you need the following:

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


source


After playing around with the solution from @malex I ended up with this version of Swift :

1) Don't forget to make your view controller UITextFieldDelegate:

class LoginViewController: UIViewController, UITextFieldDelegate {
...
}

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

      

2) use this:

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
}

      

... and if you do this for the show / hide password function, you will most likely need to save and restore the carriage position when you enable / disable secureTextEntry. Here's how to do it inside the switch method:

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


source


We solved this based on dwsolberg's answer with two fixes:

  • the password text is duplicated when clicking on the already focused password field
  • the last character of the password was found when clicking in the password field
  • deleteBackward

    and insertText

    raises an event with a changed value

So we came up with this (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


source


This is a quick code from my project that has been tested and deals with backspace and secure-entry false / true changes

// get user input and call validation methods

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


source


IOS 12, Swift 4

  • Doesn't display the last character when the text box becomes first responder again.
  • Does not duplicate existing text when tapping the text box again.

If you want to use this solution for more than just secure text entry, add the isSecureTextEntry check.

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


source


Subclass UITextField and override the following two methods:

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

      

Use this class name as the class name for the text box.

+1


source


I realize this is a little old, but in iOS 6 UITextField "text" is now the default "Attributed" in Interface Builder. Switching this to Normal, as it did in iOS 5, fixes this issue.

Also posted this same answer in a question linked by @Craig.

0


source


I had the same problem but got a 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;
    }

      

0


source


My solution (until the bug is fixed, I suppose) is to subclass UITextField in such a way that it is appended to the existing text instead of being cleared as before (or as in iOS 6). Attempts to keep the original behavior when not running on 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


source


I have experimented with dwsolberg and fluidsonic answers and this seems to work

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


source


I needed to customize @ thomas-verbeek's solution by adding a property that deals with the case when the user tries to paste any text into the field (text is duplicated)

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


source


I used @EmptyStack's answer textField.clearsOnBeginEditing = NO;

in my password textbox passwordTextField.secureTextEntry = YES;

but it didn't work in iOS11 SDK with Xcode 9.3, so I made the following code to achieve. Actually I want to keep the text (in the textbox) if the user switches between different fields.

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

      

I returned false in shouldChangeCharactersInRange

and manipulated as I want this code to also work if the user clicks the delete button to delete the character.

0


source


when my project upgrades to iOS 12.1 and this issue occurs. This is my solution for this. It works well.



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


source


Thanks for the answers before me. It seems that all one has to do is delete and paste the text into the same string object immediately after setting isSecureTextEntry. So I added the extension below:

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

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

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

      

0


source


There is another question with a stackoverflow question: Question

Reading this post it looks like you need to set textField.clearsOnBeginEditing = NO in the textFieldShouldBeginEditing delegate method

-1


source


Save the text entered into the property. For example:

NSString *_password;

      

And then in the delegate:

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

      

-2


source







All Articles