Vince O'Sullivan
Vince O'Sullivan

Reputation: 2701

Show/Hide Password Toggle. Bug or Feature?

I'm developing an iPad application using XCode and Objective-C. I have a UI that includes a password entry field and a show/hide button (code shown below). A tester has pointed out the following inconsistent behaviour.

If the password is hidden and half typed in (e.g. "abc") and the user hits the toggle button to show the password and continues typing then the new characters (e.g. "def") are added to the end of the initial entry (making "abcdef"). All well and good.

However, if the password is shown and half typed in (e.g. "abc") and the user hits the toggle button to hide the password and continues typing then the new characters (e.g. "def") replace the initial entry (making "def"). So the show/hide toggle not only shows or hides the text but also changes the behaviour of the UITextField (append / clear and start over) when the next character is entered.

I can think of (not very good) reasons why this behaviour is by design, but no one that I've shown it to thinks that it is good. Can anyone suggest a quick fix (that prevents the toggle to hide text action from clearing a part entered password)?

- (IBAction)togglePasswordReveal:(id)sender
{
    if ([self.revealButton.titleLabel.text isEqualToString:@"Show"]) {
        self.password.secureTextEntry = NO;
        [self.revealButton setTitle:@"Hide" forState:UIControlStateNormal];
    }
    else {
        self.password.secureTextEntry = YES;
        [self.revealButton setTitle:@"Show" forState:UIControlStateNormal];
    }
}

Upvotes: 3

Views: 2965

Answers (2)

Rahul
Rahul

Reputation: 21

Faced the same issue. Here's my understanding and solution.

Changing clearsOnInsertion, clearsOnBeginEditing has no effect on a secure text field.

Toggling secureTextEntry on a text field causes it to lose responder status and become first responder again. So, any new text clears the current text.

I solved it by overriding textField:shouldChangeCharactersInRange:replacementString: and a few other changes for security reasons. I'm using the right view to display the toggle button while editing.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    // Customize text entry in password field
    if (textField == self.passwordTf) {
        NSString *currentString = textField.text;

        // Handle inserting, deleting characters
        textField.text = [currentString stringByReplacingCharactersInRange:range withString:string];

        // 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];

        // If password cleared, updated edited status
        if (textField.text.length == 0) {
            passwordEdited = YES;
        }

        [self toggleButtonStatus];

        return NO;
    }

    return YES;
}

- (void)textFieldDidBeginEditing:(UITextField *)textField
{        
    // Reset password edited status on begin editing
    if (textField == self.passwordTf &&
        [textField.text length] > 0) {
        passwordEdited = NO;
    }

    [self toggleButtonStatus];
}

- (void)textFieldDidChange:(NSNotification*)notification
{
    UITextField *textField = notification.object;

    // If password cleared, updated edited status
    if (self.passwordTf == textField &&
        textField.text.length == 0) {
        passwordEdited = YES;
    }

    [self toggleButtonStatus];
}

- (void)toggleButtonStatus
{
    // For security, only show eye button if not saved password
    if (passwordEdited) {
        self.passwordTf.clearButtonMode = UITextFieldViewModeNever;
        self.passwordTf.rightViewMode = UITextFieldViewModeWhileEditing;
    } else {
        self.passwordTf.clearButtonMode = UITextFieldViewModeWhileEditing;
        self.passwordTf.rightViewMode = UITextFieldViewModeNever;
    }
}

As a bonus, here's my code for the toggle button, updating the cursor position on toggle.

eyeButton = [UIButton buttonWithType:UIButtonTypeCustom];
eyeButton.frame = CGRectMake(0, 0, 44, 44);
eyeButton.imageEdgeInsets = UIEdgeInsetsMake(0, 16, 0, 0);
[eyeButton addTarget:self action:@selector(eyeButtonPressed:) forControlEvents:UIControlEventTouchDown];
[eyeButton addTarget:self action:@selector(eyeButtonReleased:) forControlEvents:UIControlEventTouchUpInside];
[eyeButton addTarget:self action:@selector(eyeButtonReleased:) forControlEvents:UIControlEventTouchUpOutside];
[eyeButton addTarget:self action:@selector(eyeButtonReleased:) forControlEvents:UIControlEventTouchCancel];
[eyeButton addTarget:self action:@selector(eyeButtonReleased:) forControlEvents:UIControlEventTouchDragExit];

...

self.passwordTf.rightView = eyeButton;
self.passwordTf.rightViewMode = UITextFieldViewModeWhileEditing;

- (void)eyeButtonPressed:(id)sender {
    UIFont *textFieldFont = ...
    UIColor *textFieldColor = ...

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

    // Change secure entry
    self.passwordTf.secureTextEntry = NO;
}

- (void)eyeButtonReleased:(id)sender {
    UIFont *textFieldFont = ...
    UIColor *textFieldColor = ...

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

Let me know if you need any clarification or if you find any bugs. ;) Enjoy!

Upvotes: 1

Wain
Wain

Reputation: 119031

I can't find the documentation for it, but I recall that changing the secure entry setting also changes clearsOnInsertion.

Look at setting clearsOnInsertion and / or using the delegate methods textFieldShouldClear: and textField:shouldChangeCharactersInRange:replacementString: to modify this behaviour.

I guess the logic is that the user will generally not be sure what they previously typed as they can't see the characters so the sage option is to force the user to start over.

Upvotes: 2

Related Questions