iOS 6 UITextField Secure - How to detect backspace

2019-02-08 11:23发布

问题:

In iOS 6 if you type text into a secure text field, change to another text field, then come back to the secure text field and hit backspace, all of the characters are removed. I am fine with this happening, however, I am trying to enable/disable a button based on if this secure text field has characters in it or not. I know how to determine what characters are in the fields and if a backspace is hit but I am having trouble determining how to detect if clearing of all the characters is happening.

This is the delegate method I'm using to get the new text of a field, but, I can't seem to figure out how to get the new text (assuming the new text would just be a blank string) if a backspace is hit that clears all the characters.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    //returns the "new text" of the field
    NSString * text = [textField.text stringByReplacingCharactersInRange:range withString:string];
}

Any help is much appreciated.

Thanks!

回答1:

I use this solution. It does not need local variables and sets the cursor position correctly, after deleting the char.

It's a mashup of this solutions:

  • Backspace functionality in iOS 6 & iOS 5 for UITextfield with 'secure' attribute
  • how to move cursor in UITextField after setting its value

 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
    {
        if (range.location > 0 && range.length == 1 && string.length == 0)
        {
            // Stores cursor position
            UITextPosition *beginning = textField.beginningOfDocument;
            UITextPosition *start = [textField positionFromPosition:beginning offset:range.location];
            NSInteger cursorOffset = [textField offsetFromPosition:beginning toPosition:start] + string.length;

            // Save the current text, in case iOS deletes the whole text
            NSString *text = textField.text;


            // Trigger deletion
            [textField deleteBackward];


            // iOS deleted the entire string
            if (textField.text.length != text.length - 1)
            {
                textField.text = [text stringByReplacingCharactersInRange:range withString:string];

                // Update cursor position
                UITextPosition *newCursorPosition = [textField positionFromPosition:textField.beginningOfDocument offset:cursorOffset];
                UITextRange *newSelectedRange = [textField textRangeFromPosition:newCursorPosition toPosition:newCursorPosition];
                [textField setSelectedTextRange:newSelectedRange];
            }

            return NO;
        }

        return YES;
    }


回答2:

Finally figured it out for anyone looking to see how to determine when a backspace is going to clear all the characters of a secure UITextField:

UITextField:

self.passwordTextField

Private property (initialized to NO in init - probably not needed):

self.passwordFirstCharacterAfterDidBeginEditing

UITextFieldDelegate Methods:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    //if text is blank when first editing, then first delete is just a single space delete
    if([textField.text length] == 0 && self.passwordFirstCharacterAfterDidBeginEditing)
        self.passwordFirstCharacterAfterDidBeginEditing = NO;

    //if text is present when first editing, the first delete will result in clearing the entire password, even after typing text
    if([textField.text length] > 0 && self.passwordFirstCharacterAfterDidBeginEditing && [string length] == 0 && textField == self.passwordTextField)
    {
        NSLog(@"Deleting all characters");
        self.passwordFirstCharacterAfterDidBeginEditing = NO;
    }
    return YES;
}

-(void)textFieldDidBeginEditing:(UITextField *)textField
{
    if(textField == self.passwordTextField)
    {
        self.passwordFirstCharacterAfterDidBeginEditing = YES;
    }
}

I hope this helps someone and I also hope Apple just creates a delegate method that is called when a secure text field is cleared by a delete - this seems a big cumbersome, but it works.



回答3:

Secure text fields clear on begin editing on iOS6+, regardless of whether you're deleting or entering text. Checking if the length of the replacement string is 0 works if the text field is cleared from a backspace, but annoyingly, the range and replacement string parameters to textField:shouldChangeCharactersInRange:replacementString: are incorrect if the text is cleared when entering text. This was my solution:

1) Register for textFieldTextDidChange notifications.

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(textFieldTextDidChange:)
                                             name:UITextFieldTextDidChangeNotification
                                           object:nil];

2) Re-invoke the textField:shouldChangeCharactersInRange:replacementString: delegate method if the text field is secure and may have been cleared.

- (void)textFieldTextDidChange:(NSNotification *)notification
{
    if (textField.secureTextEntry && textField.text.length <= 1) {
        [self.textField.delegate textField:self.textField
             shouldChangeCharactersInRange:NSMakeRange(0, textField.text.length)
                         replacementString:textField.text];
    }
}


回答4:

The accepted solution does not enable a button. It actually changes backspace behaviour on a secure textfield.

This Swift solution does solve the question by correctly informing a delegate about the actual string that will be set in the textField, including when pressing backspace. A decision can then be made by that delegate.

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
    var shouldChange = true

    if let text = textField.text {
        let oldText = text.copy()
        var resultString = (text as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
        let inputString = string as NSString
        if range.location > 0 && range.length == 1 && inputString.length == 0 {//Backspace pressed
                textField.deleteBackward()
                shouldChange = false

                if let updatedText = textField.text as NSString? {
                    if updatedText.length != oldText.length - 1 {
                        resultString = ""
                    }
                }
        }
        self.delegate?.willChangeTextForCell(self, string: resultString as String)
    }

    return shouldChange
}


回答5:

I was facing the same issue, I wanted to detect when the backspace is going to clear all characters so that I can enabled disable some other buttons on screen. So this is how I achieved it.

-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
    _checkForDelete = NO;
    if (textField.isSecureTextEntry && textField.text.length > 0) {
        _checkForDelete = YES;
    }
    return YES;
}

-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    if (_checkForDelete && string.length == 0) {
        _checkForDelete = NO;
        //Do whatever you want to do in this case, 
        //because all the text is going to be cleared.
    }
    return YES
}

This solution is different because it lets you take action when a secure field already has some text in it and you are trying to edit it, rather in the above accepted answer, you will go in the block even if you hit backspace while already editing a field, that will not clear the whole text but only deletes one character. Using my method you have an option to take an action in this special case, but it will not impact the native flow either.



回答6:

Posting my alternate solution as I had the exact problem as OP and found I did not have to do any cursor position alteration detailed in the chosen answer.

Below is the UITextFieldDelegate method, I call a custom delegate method didChangeValueForTextField as my button I want to enable is outside of this class.

Class implementing UITextFieldDelegate

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *newValue = [textField.text stringByReplacingCharactersInRange:range withString:string];
BOOL shouldReturn = YES;

if (range.location > 0 && range.length == 1 && string.length == 0){
    [textField deleteBackward];

    if ([textField.text isEmptyCheck]) {  // Check if textField is empty
        newValue = @"";
    }

    shouldReturn = NO;
}

if(self.delegate && [self.delegate respondsToSelector:@selector(didChangeValueForField:withNewValue:)]) {
    [self.delegate didChangeValueForField:textField withNewValue:newValue];
}

return shouldReturn;

}

The key was detecting the difference between secure field single character deletion and secure field single character deletion that triggers field clear. Detecting this within the above delegate method was necessary.

Class containing button to enable

 - (void)didChangeValueForField:(UITextField *)textField withNewValue:(NSString *)value {
  // Update values used to validate/invalidate the button.
  // I updated a separate model here.

  // Enable button based on validity
  BOOL isEnabled = [self allFieldsValid];  //  Check all field values that contribute to the button being enabled.

  self.myButton.enabled = isEnabled;

}


回答7:

swift3.0 - it works.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

    if textField == passwordTextfield {

        if range.location > 0 && range.length == 1 && string.characters.count == 0 {

            if let textString = textField.text {
                // iOS is trying to delete the entire string
                let index = textString.index(textString.endIndex, offsetBy: -1)
                textField.text = textString.substring(to: index)
                return false
            }

        }else if range.location == 0 {
            return true
        }else {
            if let textString = textField.text {
                textField.text = textString + string
                return false
            }
        }


    }
    return true
}