Search code examples
iosobjective-cuitextfieldnsnumberformatter

Numbers separated by commas in UITextField not working


I'm trying to add a decimal after every 3 characters. (Counting backwards like this: 1,325,541 instead of 1325451.)

Here is what I tried:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    NSNumberFormatter *numberFormat = [[NSNumberFormatter alloc] init];
    [numberFormat setGroupingSeparator:@","];
    [numberFormat setGroupingSize:3];
    [numberFormat setNumberStyle:NSNumberFormatterDecimalStyle];

    NSNumber *amount = [numberFormat numberFromString:textField.text];
    textField.text = [numberFormat stringFromNumber:amount];
    return YES;
}

It doesn't insert a comma after every 3 characters. What can I do to fix it?


Solution

  • Well, this ends up being a little more nuanced than you might expect, but this is what I came up with:

    As @bgfriend0's comment mentions, textField:shouldChangeCharactersInRange:replacementString gets called before applying an edit to a text field (this is useful for other reasons). Meaning, that if your current string was 123 and a user goes to type in 4, the method would be passed the following as parameters:

    • textField: the textField, but note at this point textField.text is 123
    • range: NSRange{2, 0}
    • string: 4

    And there is a way to make this function work from within that method, but there's a better way (I think).

    After you instantiate your textField, add a target to listen for editing events:

    [textField addTarget:self 
                  action:@selector(formatNumberIfNeeded:)    
        forControlEvents:UIControlEventEditingChanged];
    

    This will get called anytime an editing change happens in a UITextField. The SEL that we'll be performing will look like this:

    - (void)formatNumberIfNeeded:(UITextField *)textField{
        // you'll need to strip the commas for the formatter to work properly
        NSString * currentTextWithoutCommas = [textField.text stringByReplacingOccurrencesOfString:@"," withString:@""];
    
        NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
        numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
    
        NSNumber * numberFromString = [numberFormatter numberFromString:currentTextWithoutCommas];
        NSString * formattedNumberString = [numberFormatter stringFromNumber:numberFromString];
    
        textField.text = formattedNumberString;
    }
    

    Now, things get slightly more tricky if you need to localize, but cross that bridge if needed.

    As for textField:shouldChangeCharactersInRange:replacementString, that's a much better place to do character validation. So, a basic validation to check for letters could look like:

    -(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
    
        NSRange illegalCharacterEntered = [string rangeOfCharacterFromSet:[NSCharacterSet letterCharacterSet]];
        if ( illegalCharacterEntered.location != NSNotFound ) {
            return NO;
        }
    
        return YES;
    }
    

    So with those two bits of code, you'll be able to update the textfield string to include a comma every 3rd character, and users wont be able to enter any letters of the alphabet (but they can still input other 'illegal' characters, so extend that validation as needed).