Search code examples
iphoneobjective-ciosnsnumberformatter

Formatting a string containing a number with comma separation


I have a number stored in an NSMutableString instance which I want to auto format with comma delimiters and then display the result in a UITextField.

I've tried using NSNumberFormatter to format as currency, but I don't want it to show decimals if the original NSMutableString doesn't contain a decimal place.

For example:

  • If the NSMutableString contains "1234567", it should format as "1,234,567".
  • If the NSMutableString contains "1234567.1", it should format as "1,234,567.1"
  • If the NSMutableString contains "1234567.12", it should format as "1,234,567.12"

The maximum decimals that the NSMutableString will contain is 2.

Any help is greatly appreciated.

Thanks!


Solution

  • Keep in mind that you should really be localizing this if you are interacting with users on this, however here is one way to do it:

    - (NSString *)formatString:(NSString *)string {
        // Strip out the commas that may already be here:
        NSString *newString = [string stringByReplacingOccurrencesOfString:@"," withString:@""];
        if ([newString length] == 0) {
            return nil;
        }
    
        // Check for illegal characters
        NSCharacterSet *disallowedCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet];
        NSRange charRange = [newString rangeOfCharacterFromSet:disallowedCharacters];
        if ( charRange.location != NSNotFound) {
            return nil;
        }
    
        // Split the string into the integer and decimal portions
        NSArray *numberArray = [newString componentsSeparatedByString:@"."];
        if ([numberArray count] > 2) {
            // There is more than one decimal point
            return nil;
        }
    
        // Get the integer
        NSString *integer           = [numberArray objectAtIndex:0];
        NSUInteger integerDigits    = [integer length];
        if (integerDigits == 0) {
            return nil;
        }
    
        // Format the integer.
        // You can do this by first converting to a number and then back to a string,
        // but I would rather keep it as a string instead of doing the double conversion.
        // If performance is critical, I would convert this to a C string to do the formatting.
        NSMutableString *formattedString = [[NSMutableString alloc] init];
        if (integerDigits < 4) {
            [formattedString appendString:integer];
        } else {
            // integer is 4 or more digits
            NSUInteger startingDigits = integerDigits % 3;
            if (startingDigits == 0) {
                startingDigits = 3;
            }
            [formattedString setString:[integer substringToIndex:startingDigits]];
            for (NSUInteger index = startingDigits; index < integerDigits; index = index + 3) {
                [formattedString appendFormat:@",%@", [integer substringWithRange:NSMakeRange(index, 3)]];
            }
        }
    
        // Add the decimal portion if there
        if ([numberArray count] == 2) {
            [formattedString appendString:@"."];
            NSString *decimal = [numberArray objectAtIndex:1];
            if ([decimal length] > 0) {
                [formattedString appendString:decimal];
            }
        }
    
        return formattedString;
    }
    
    // Test cases:
    NSLog(@"%@", [self formatString:@"123456"]);
    NSLog(@"%@", [self formatString:@"1234567."]);
    NSLog(@"%@", [self formatString:@"12345678.1"]);
    NSLog(@"%@", [self formatString:@"123456789.12"]);
    
    // Output:
    123,456
    1,234,567.
    12,345,678.1
    123,456,789.12