27

The above says it all- I have a UITextField set to secure, but want to give users the option to make it not secure (so they can see for sure what they typed if they are in a private area). However, suppose they hit the toggle by mistake, and want to change it back to secure mode? That does not work. I've tried everything - using -1 instead of YES, deleting the text and then putting it back. I'm at a total loss on other ideas. [Entered rdar://9781908]

EDIT: I believe this issue is fixed as of iOS5.

David H
  • 40,852
  • 12
  • 92
  • 138
  • // touchUp from a UISwitch that sets what the state should be // works in ON->OFF, but changing it OFF->ON has no effect, / switch stays in non-secure mode from then on - (IBAction)secureSwitchAction:(UISwitch *)sender { BOOL isOn = sender.on; textField.secureTextEntry = isOn; } – David H Jul 15 '11 at 17:36
  • 1
    thanku and take care. :) – Vijay-Apple-Dev.blogspot.com Jul 18 '11 at 17:42

5 Answers5

46

It must be input-focus issue: when focused, UITextField can change only ON->OFF.
Try next trick to switch OFF->ON:

textField.enabled = NO;
textField.secureTextEntry = YES;
textField.enabled = YES;
[textField becomeFirstResponder];

EDIT (dhoerl): In iOS 9.3, I found this works but there is a problem. If you enter say 3 characters in plain view, then switch to secure text, then type a character, the 3 pre-existing characters disappear. I tried all kinds of trick to clear, then reset the text without success. I finally tried just playing with the control by cutting and pasting - pasting into the newly-switched-to-secure-mode worked great. So I emulated it in code, and now it all works fine - no need to play with the responder either. Here is what I finally ended up with:

    if textview should go into secure mode and the textfield is not empty {
        textField.text = ""
        textField.secureTextEntry = true

        UIPasteboard.generalPasteboard().string = password
        textField.paste(self)
        UIPasteboard.generalPasteboard().string = ""
    }

I'd prefer to not have to do this paste, but if you want it seamless this is the only way I can find to do it.

David H
  • 40,852
  • 12
  • 92
  • 138
MikeR
  • 609
  • 6
  • 12
  • 1
    Yes - I did discover that as long as the control is not the first responder, that it works as expected. A little bird told me that this might be addressed in a future release of iOS. – David H Aug 22 '11 at 17:10
  • So I tested your code above in liu of my fix - the textField apparently resigns first responder when it's diabled. That said, appending [textField becomeFirstResponder]; results in a working solution much simpler than mine. Thanks! – David H Aug 22 '11 at 18:07
  • FYI I tried this solution and resulted in an extra space after secureTextEntry was set to NO: http://stackoverflow.com/questions/14220187/uitextfield-has-trailing-whitespace-after-securetextentry-toggle – Brian Colavito Jan 08 '13 at 19:22
  • 1
    This solution doesn't work (at least on iOS 8.1) when toggling back and forth repeatedly. – sethfri Nov 13 '14 at 11:13
  • 1
    Tested on iOS 8.1 and it works. FYI you only need the becomeFirstResponder line. – jomafer Nov 25 '14 at 10:22
  • @jomafer can you clarify - what do you mean, only need one line - do you mean you use two lines, change the secureTextEntry, then make it the first responder again? Please tell us exactly what messages are needed. – David H Dec 18 '14 at 14:43
23

Mike R's solution is nice, but I prefer this approach:

BOOL wasFirstResponder;
if ((wasFirstResponder = [passwordField isFirstResponder])) {
    [passwordField resignFirstResponder];
}
// In this example, there is a "show password" toggle
[passwordField setSecureTextEntry:![passwordField isSecureTextEntry]];
if (wasFirstResponder) {
    [passwordField becomeFirstResponder];
}

That way you only becomeFirstResponder again when necessary.

Sandy
  • 1,968
  • 14
  • 25
  • This does seem like a better solution to me, thanks Sandy. I've wrapped it up in a little function for my app. – JosephH Mar 20 '12 at 20:00
  • I just upvoted this answer because when I tried the first answer, I got a mysterious trailing space at the end of my text when going to setting secureTextEntry to NO. This solution doesn't have that issue. – Brian Colavito Jan 08 '13 at 21:35
  • 2
    Am i alone to have a bug when toggle OFF ==> ON ? If I enter new letters, the content is cleared first ;-( – Thomas Decaux Sep 24 '13 at 08:45
  • This solution doesn't work (at least on iOS 8.1) when toggling back and forth repeatedly. – sethfri Nov 13 '14 at 11:13
1

I entered rdar against this problem, but did find a work around. Essentially you have to programmatically replace the "stuck" control with a new one. The easiest thing to do is to archive the existing control in viewDidLoad then unarchive as needed:

// do in viewDidLoad
self.passwordMemberArchive = [NSMutableData data];
NSKeyedArchiver *ka = [[NSKeyedArchiver alloc]       initForWritingWithMutableData:passwordMemberArchive];
[ka encodeObject:password];
[ka finishEncoding];
[ka release];


    // In the action method when you get the UISwitch action message ---
// when your switch changes state
if(isOn) {
    NSString *text = [NSString stringWithString:password.text];
    NSKeyedUnarchiver *kua = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    UITextField *tf = [kua decodeObject];
    [kua finishDecoding];
    [kua release];
    tf.inputAccessoryView = textField.inputAccessoryView;
    tf.frame = textField.frame;

    BOOL isFirstResponder = [textField isFirstResponder];
    [scrollView insertSubview:tf aboveSubview:textField];
    if(isFirstResponder) {
        [tf becomeFirstResponder];
    }

    [textField removeFromSuperview];
    self.password = tf;

    if([text length]) {
        if(isFirstResponder) {
            // http://stackoverflow.com/questions/1317929/insert-string-at-cursor-position-of-uitextfield
            // Get a reference to the system pasteboard
            UIPasteboard* lPasteBoard = [UIPasteboard generalPasteboard];
            // Save the current pasteboard contents so we can restore them later
            NSArray* lPasteBoardItems = [lPasteBoard.items copy];
            // Update the system pasteboard with my string
            lPasteBoard.string = text;
            // Paste the pasteboard contents at current cursor location
            [tf paste:self];
            // Restore original pasteboard contents
            lPasteBoard.items = lPasteBoardItems;
            [lPasteBoardItems release];
        } else {
            tf.text = text;
        }
    }
} else {
    textField.secureTextEntry = NO;
}
David H
  • 40,852
  • 12
  • 92
  • 138
0

With iOS, you should never try to "hack" stuff, if the behavior you want is not provided by the framework, change your mind !

First its easier ^^, second the user will not be responsive to this, then you never know if the next iOS update will break it or not, so it can be dangerous for your application.

"You want the user sees the password he is taping on a secured textfield", you can display a UILabel in the bottom instead ? Or a confirmation Alert box with the clear password ?

Thomas Decaux
  • 21,738
  • 2
  • 113
  • 124
  • Funny, the feature has been in a popular app, rated 5 stars, in the store for two years, and comments on this are positive. Note that the ability to change the option was there, and Apple even fixed this problem after it was reported. – David H Sep 24 '13 at 11:44
  • Yes the feature is here, of course this feature is a good idea, I never said that ! I just said you should consider use a clean way (as iOS code I mean) to make your feature alive ! – Thomas Decaux Sep 25 '13 at 08:16
0

Swift version of Sandy's solution.

if #available(iOS 9.2, *) {
    passwordTextField.secureTextEntry = !passwordTextField.secureTextEntry
}
else {
    let wasFirstResponder = passwordTextField.isFirstResponder()
    if wasFirstResponder {
        passwordTextField.resignFirstResponder()
    }

    passwordTextField.secureTextEntry = !passwordTextField.secureTextEntry
    if wasFirstResponder {
        passwordTextField.becomeFirstResponder()
    }
}
David Rees
  • 6,792
  • 3
  • 31
  • 39