Brian Colavito
Brian Colavito

Reputation: 891

UITextField has trailing whitespace after secureTextEntry toggle

I have a button that toggles between Show/Hide mode (i.e. toggles a UITextField between secureTextEntry NO and YES). The purpose of which is to allow the user to see the password they are entering.

I followed the example (with the highest number of votes) here: UITextField secureTextEntry - works going from YES to NO, but changing back to YES has no effect

However, when I set secureTextEntry to NO, any text that was written there ends up with a space at the end. This does not seem to be a problem when setting secureTextEntry to YES.

For example, if I enter the text "mypassword" while setSecureTextEntry is set to NO, and then switch it to YES, the user will see ********** (10 dots), which is correct. If I setSecureTextEntry to NO, the user will see "mypassword " (with a space at the end, or at least, the cursor moved one space to the right).

Important note: In the debugger, the string value of text appears without the trailing space, like this:

(lldb) expr self.passwordText.text
(NSString *) $0 = 0x1d8450e0 @"mypassword"

I have tried trimming whitespace (per avoid middle whitespace in UITextField), but it has had no effect.

Upvotes: 35

Views: 10299

Answers (18)

redmage1993
redmage1993

Reputation: 11

I had a similar issue and realized it was because I was updating the text before setting the secureTextEntry property. It makes sense that the textField would draw out the caret at the location it'd be at if it were using secureTextEntry.

I did not read the entire problem nor did I visit the solution linked by OP, but in case someone else has the same issue as me:

Try updating your text after setting the secureTextEntry property.

Upvotes: 0

anilgoktas
anilgoktas

Reputation: 109

Swift UITextField extension:

extension UITextField {
    func toggleSecureEntry() {
        let wasFirstResponder = isFirstResponder

        if wasFirstResponder { resignFirstResponder() }
        isSecureTextEntry.toggle()
        if wasFirstResponder { becomeFirstResponder() }
    }
}

Setting textField.text solution also works in some situations but not for my need (Custom font with two text fields. Caused font changes and glitches on runtime.) Adding here too.

func toggleSecureEntry() {
    isSecureTextEntry.toggle()
    let originalText = text
    text = nil
    text = originalText
}

Upvotes: 1

dimpiax
dimpiax

Reputation: 12687

Swift 4

Bug is on radar, there is explanation of workaround also: http://www.openradar.me/38465011

Here is cut of temporary workaround how to natively update caret (cursor) position.

// update caret position
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
    let (beginning, end) = (self.beginningOfDocument, self.endOfDocument)

    self.selectedTextRange = self.textRange(from: beginning, to: end)
    self.selectedTextRange = self.textRange(from: end, to: end)
}

Upvotes: 0

sunus
sunus

Reputation: 838

i've just encounter this case and finally solved this problem.

works on Latest iOS SDK, iOS 8.1

First of all, there is no trailing space at all.

The dot(shown in SecureEntry) character and normal character have different width and after you toggle isSecureEntry switch, the cursor didn't refresh it's position.

so i use this workaround to solved this problem.

- (void)toggle
{
    NSString *tmpString;
    [self.passwordTextField setSecureTextEntry:!self.passwordTextField.isSecureTextEntry];
    if (self.passwordTextField.isSecureTextEntry) {
       // do stuffs
    } else {
       // do other stuffs
    }
    // Workaround to refresh cursor
    tmpString = self.passwordTextField.text;
    self.passwordTextField.text = @" ";
    self.passwordTextField.text = tmpString;
}

Swift 3+

   // Workaround to refresh cursor

    let currentText: String = self.userPassword.text!
    self.userPassword.text = "";
    self.userPassword.text = currentText

hope it helps!

Upvotes: 42

Ramdhas
Ramdhas

Reputation: 1765

I'm using this, Works fine.

[self.yourTextField becomeFirstResponder];

Upvotes: 0

DHARMENDRA SINHA
DHARMENDRA SINHA

Reputation: 1

Here is the solution:

- (void)showHidePassword:(UIButton *)sender {
    EDSignUpCell *cell = [self.signUpTblView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:3 inSection:0]];
    if(!TRIM_SPACE(cell.cellTextField.text).length) {return;}
    [cell.showHidePasswordBtn setSelected:!cell.showHidePasswordBtn.isSelected];
    cell.cellTextField.secureTextEntry = cell.showHidePasswordBtn.isSelected;
    [cell.cellTextField setText:cell.cellTextField.text];
    [cell.cellTextField becomeFirstResponder];
}

Upvotes: 0

Hans-Peter
Hans-Peter

Reputation: 1

Everytime the text is set in the UITextField, the cursor postition is updated

So I used this code

partial void btnShowPassword_ToutchUpInside (UIButton sender)
    {
        if (TxtPassword.SecureTextEntry == true) {
            TxtPassword.SecureTextEntry = false;
            TxtPassword.Text = TxtPassword.Text;
        } else {
            TxtPassword.SecureTextEntry = true;
        }
    }

Upvotes: 0

tounaobun
tounaobun

Reputation: 14857

I have a clean solution not going dirty with text property of UITextField.

Wrap them in this style.

[self.passwordTextField resignFirstResponder]; // first resign its first responder.

// change `secureTextEntry` property's value if necessary.

if (self.passwordTextField.secureTextEntry) {
    self.passwordTextField.secureTextEntry = NO;
    self.passwordEcryptButton.selected = YES;
}else{
    self.passwordTextField.secureTextEntry = YES;
    self.passwordEcryptButton.selected = NO;
}

[self.passwordTextField becomeFirstResponder];  // finally gain its first responder again.

Upvotes: 8

Kingiol
Kingiol

Reputation: 1145

You can fix it like this:

NSString *currentText = self.textfield.text;
self.textfield.text = @"";
self.textfield.text = currentText;

Upvotes: 4

Rahul
Rahul

Reputation: 21

To get the cursor to reposition correctly, setting the font attributes seemed to do the trick for me.

// Hack to update cursor position
self.passwordTf.defaultTextAttributes = @{NSFontAttributeName: textFieldFont, NSForegroundColorAttributeName: textFieldColor};

// Change secure entry
self.passwordTf.secureTextEntry = !self.passwordTf.secureTextEntry;

Tested on iOS8, iOS9. Hope it helps!

Upvotes: 0

Matthew
Matthew

Reputation: 541

PRE-iOS-8.0 (dated solution)... In your button's action method (toggling between secureTextEntry YES and NO), simply set UITextField's text property to its current text value. Although this may seem redundant and a bit like a hack, this will redraw the cursor in the right position. Here's an example of what your button's action method should look like now...

-(void)toggleButtonPressed:(UIButton*)sender
{
    // Toggle between secure and not-so-secure entry
    self.toggleButton.selected = !self.toggleButton.selected;
    self.textfield.secureTextEntry = !self.toggleButton.selected;

    // * Redundant (but necessary) line *
    [self.textfield setText:self.textfield.text];
}

POST-iOS-8.0... As of iOS 8.0, it appears that UITextField's text setter no longer redraws the cursor when called with a string equal to its current string value. Now, we need to take this a step further and actually change the text value before resetting it again. Replace the above setText: line with something like these lines.

// * Extra redundant (but necessary) lines *
NSString *currentText = self.textfield.text;
[self.textfield setText:@"Arbitrary string..."]; // Change the value
[self.textfield setText:currentText]; // Reset the value

Upvotes: 13

Mihir Mehta
Mihir Mehta

Reputation: 13843

This Works in my case

    BOOL wasFirstResponder = [self.passwordTextField isFirstResponder];
    if([self.passwordTextField isSecureTextEntry])
    {
        //This three lines are key
        self.passwordTextField.delegate = nil;
        [self.passwordTextField resignFirstResponder];
        self.passwordTextField.delegate = self;
    }

    [self.passwordTextField setSecureTextEntry: !self.passwordTextField.isSecureTextEntry];
    if(wasFirstResponder)
        [self.passwordTextField becomeFirstResponder];

Upvotes: 1

nalexn
nalexn

Reputation: 10801

In order to work around this bug in iOS you can simply do the following (works for any iOS version):

- (IBAction)toggleSecureTextEntry:(UIButton *)button
{
    self.textField.secureTextEntry = !self.textField.secureTextEntry;
    NSString *originalText = self.textField.text;
    self.textField.text = nil;
    self.textField.text = originalText;
}

Upvotes: 6

mc_plectrum
mc_plectrum

Reputation: 391

This is another possibility to solve this issue, where self.passwordText is the UITextField:

if (self.passwordText.isFirstResponder) {
    [self.passwordText resignFirstResponder];
    [self.passwordText becomeFirstResponder];
}

Upvotes: 2

KeepWalking
KeepWalking

Reputation: 129

When we change a textfield.secureTextEntry property, the caret position is not updated. To fix this, the code below used to work before IOS 8:

pwdTextField.text  = pwdTextField.text

Now it doesn't. It seems IOS 8 detects the new value equals old value and does nothing. So to make it work again we have to actually change the value. Here is the swift version that works for me.

let str = pwdTextField.text
pwdTextField.text = str + " "
pwdTextField.text = str

Upvotes: 2

David Carvalho
David Carvalho

Reputation: 118

UITextPosition *beginning = [self.passwordField beginningOfDocument];
[self.passwordField setSelectedTextRange:[self.passwordField textRangeFromPosition:beginning
                                                                        toPosition:beginning]];

UITextPosition *end = [self.passwordField endOfDocument];
[self.passwordField setSelectedTextRange:[self.passwordField textRangeFromPosition:end
                                                                        toPosition:end]];

This is what I used for iOS 8

Upvotes: 3

Gonzo Oin
Gonzo Oin

Reputation: 379

This work for me on iOS 8

if (self.passwordTextField.secureTextEntry) {
    // Display password and keep selected text range
    UITextRange *selectedTextRange = self.passwordTextField.selectedTextRange;
    NSString *password = self.passwordTextField.text;
    self.passwordTextField.secureTextEntry = NO;
    self.passwordTextField.text = [@"" stringByPaddingToLength:password.length withString:@" " startingAtIndex:0]; // Done for carret redrawing
    self.passwordTextField.text = password;
    self.passwordTextField.selectedTextRange = selectedTextRange;
}
else {
    // Hide password and keep selected text range
    UITextRange *selectedTextRange = self.passwordTextField.selectedTextRange;
    NSString *password = self.passwordTextField.text;
    self.passwordTextField.secureTextEntry = YES;
    self.passwordTextField.text = [@"" stringByPaddingToLength:password.length withString:@" " startingAtIndex:0]; // Done for carret redrawing
    self.passwordTextField.text = password;
    self.passwordTextField.selectedTextRange = selectedTextRange;
}

Upvotes: 3

Brian Colavito
Brian Colavito

Reputation: 891

It appears that the second solution in the referenced link, when implemented, has the desired behavior of not adding an extra space:

https://stackoverflow.com/a/8495888/738190

Upvotes: 1

Related Questions