How do you dynamically format a number to have commas in a UITextField entry?

Here is a version in Swift 4. I used it for integers, I didn't check with decimal numbers.

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

     // Uses the number format corresponding to your Locale
     let formatter = NumberFormatter()
     formatter.numberStyle = .decimal
     formatter.locale = Locale.current
     formatter.maximumFractionDigits = 0


    // Uses the grouping separator corresponding to your Locale
    // e.g. "," in the US, a space in France, and so on
    if let groupingSeparator = formatter.groupingSeparator {

        if string == groupingSeparator {
            return true
        }


        if let textWithoutGroupingSeparator = textField.text?.replacingOccurrences(of: groupingSeparator, with: "") {
            var totalTextWithoutGroupingSeparators = textWithoutGroupingSeparator + string
            if string.isEmpty { // pressed Backspace key
                totalTextWithoutGroupingSeparators.removeLast()
            }
            if let numberWithoutGroupingSeparator = formatter.number(from: totalTextWithoutGroupingSeparators),
                let formattedText = formatter.string(from: numberWithoutGroupingSeparator) {

                textField.text = formattedText
                return false
            }
        }
    }
    return true
}

The big advantage of this method is that it uses the grouping separator defined in your current locale (region), because not everybody uses the comma as a grouping separator.

Works with 0, backspace, but, again, I didn't test it with decimals. You are free to enhance this code if you worked it out with decimals.

Examples:

  • Enter : "2" -> "2"
  • Enter : "3" -> "23"
  • Enter : "6" -> "236"
  • Enter : "7" -> "2,367"
  • Enter : "0" 3 times -> "2,367,000"
  • Backspace -> "236,700"

Starting 0 works too:

  • Enter : "0" -> "0"
  • Enter : "2" -> "2"

Instead of inserting the commas on your own in shouldChangeCharactersInRange:, you can use an NSNumberFormatterDecimalStyle to handle the comma formatting for you. Even though it's called "decimal" style, it also inserts commas to appropriately group numbers into their thousands digits.

Note: To simplify matters, I'll assume you only want the text field to accept numeric entries and I'll also add logic to limit the user's input to numbers.

Edit: I've updated the code to handle decimals also as per the OP's request.

To utilize NSNumberFormatterDecimalStyle's formatting upon every character entry, try adding this to your shouldChangeCharactersInRange: delegate method:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    if (([string isEqualToString:@"0"] || [string isEqualToString:@""]) && [textField.text rangeOfString:@"."].location < range.location) {
        return YES;
    }

    // First check whether the replacement string's numeric...
    NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
    NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
    bool isNumeric = [string isEqualToString:filtered];

    // Then if the replacement string's numeric, or if it's 
    // a backspace, or if it's a decimal point and the text
    // field doesn't already contain a decimal point,
    // reformat the new complete number using
    // NSNumberFormatterDecimalStyle
    if (isNumeric ||
        [string isEqualToString:@""] ||
        ([string isEqualToString:@"."] &&
         [textField.text rangeOfString:@"."].location == NSNotFound)) {

        // Create the decimal style formatter
        NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
        [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
        [formatter setMaximumFractionDigits:10];

        // Combine the new text with the old; then remove any
        // commas from the textField before formatting
        NSString *combinedText = [textField.text stringByReplacingCharactersInRange:range withString:string];
        NSString *numberWithoutCommas = [combinedText stringByReplacingOccurrencesOfString:@"," withString:@""];
        NSNumber *number = [formatter numberFromString:numberWithoutCommas];

        NSString *formattedString = [formatter stringFromNumber:number];

        // If the last entry was a decimal or a zero after a decimal,
        // re-add it here because the formatter will naturally remove
        // it.
        if ([string isEqualToString:@"."] &&
            range.location == textField.text.length) {
            formattedString = [formattedString stringByAppendingString:@"."];
        }

        textField.text = formattedString;

    }

    // Return no, because either the replacement string is not 
    // valid or it is and the textfield has already been updated
    // accordingly
    return NO;
}

Format the number with grouping attributes as shown here.

NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setGroupingSeparator:@","];
[numberFormatter setGroupingSize:3];
[numberFormatter setDecimalSeparator:@"."];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:3];
[numberFormatter setMinimumFractionDigits:3];

Output for the above code is

1,234,567.850

For Swift 4.0 Version of Lyndsey Scott's answer:

   func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if ((string == "0" || string == "") && (textField.text! as NSString).range(of: ".").location < range.location) {
            return true
        }

        // First check whether the replacement string's numeric...
        let cs = NSCharacterSet(charactersIn: "0123456789.").inverted
        let filtered = string.components(separatedBy: cs)
        let component = filtered.joined(separator: "")
        let isNumeric = string == component

        // Then if the replacement string's numeric, or if it's
        // a backspace, or if it's a decimal point and the text
        // field doesn't already contain a decimal point,
        // reformat the new complete number using
        if isNumeric {
            let formatter = NumberFormatter()
            formatter.numberStyle = .decimal
            formatter.maximumFractionDigits = 8
            // Combine the new text with the old; then remove any
            // commas from the textField before formatting
            let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
            let numberWithOutCommas = newString.replacingOccurrences(of: ",", with: "")
            let number = formatter.number(from: numberWithOutCommas)
            if number != nil {
                var formattedString = formatter.string(from: number!)
                // If the last entry was a decimal or a zero after a decimal,
                // re-add it here because the formatter will naturally remove
                // it.
                if string == "." && range.location == textField.text?.count {
                    formattedString = formattedString?.appending(".")
                }
                textField.text = formattedString
            } else {
                textField.text = nil
            }
        }
        return false

    }