I was wondering how I can format the textField that I'm using for a phone number (ie like the "Add New Contact" page on the iPhone. When I enter in a new mobile phone, ex. 1236890987 it formats it as (123) 689-0987.) I already have the keyboard set as the number pad.


Here is my solution.. works great! Formats the phone number in realtime. Note: This is for 10 digit phone numbers. And currently it auto formats it like (xxx) xxx-xxxx.. tweak to your hearts delight.

First in your shouldChangeCharactersInRange you want to gather the whole string for the phone text field and pass it to the validation/formatting function.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString* totalString = [NSString stringWithFormat:@"%@%@",textField.text,string];

// if it's the phone number textfield format it.
if(textField.tag==102 ) {
    if (range.length == 1) {
        // Delete button was hit.. so tell the method to delete the last char.
        textField.text = [self formatPhoneNumber:totalString deleteLastChar:YES];
    } else {
        textField.text = [self formatPhoneNumber:totalString deleteLastChar:NO ];
    }
    return false;
}

return YES; 
}

And here is where the phone number is formatted. The regex could probably be cleaned up a bit. But I have tested this code for a while and seems to pass all bells. Notice we also use this function to delete a number in the phone number. Works a little easier here because we already stripped out all the other non digits.

 -(NSString*) formatPhoneNumber:(NSString*) simpleNumber deleteLastChar:(BOOL)deleteLastChar {
if(simpleNumber.length==0) return @"";
// use regex to remove non-digits(including spaces) so we are left with just the numbers 
NSError *error = NULL;
 NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[\\s-\\(\\)]" options:NSRegularExpressionCaseInsensitive error:&error];
 simpleNumber = [regex stringByReplacingMatchesInString:simpleNumber options:0 range:NSMakeRange(0, [simpleNumber length]) withTemplate:@""];

// check if the number is to long
if(simpleNumber.length>10) {
    // remove last extra chars.
    simpleNumber = [simpleNumber substringToIndex:10];
}

if(deleteLastChar) {
    // should we delete the last digit?
    simpleNumber = [simpleNumber substringToIndex:[simpleNumber length] - 1];
}

// 123 456 7890
// format the number.. if it's less then 7 digits.. then use this regex.
if(simpleNumber.length<7)
simpleNumber = [simpleNumber stringByReplacingOccurrencesOfString:@"(\\d{3})(\\d+)"
                                                           withString:@"($1) $2"
                                                              options:NSRegularExpressionSearch
                                                                range:NSMakeRange(0, [simpleNumber length])];

else   // else do this one..
    simpleNumber = [simpleNumber stringByReplacingOccurrencesOfString:@"(\\d{3})(\\d{3})(\\d+)"
                                                           withString:@"($1) $2-$3"
                                                              options:NSRegularExpressionSearch
                                                                range:NSMakeRange(0, [simpleNumber length])];
return simpleNumber;
}

Here's how you can do it in Swift 4:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
    if (textField == phoneTextField) {
        let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
        let components = newString.components(separatedBy: NSCharacterSet.decimalDigits.inverted)

        let decimalString = components.joined(separator: "") as NSString
        let length = decimalString.length
        let hasLeadingOne = length > 0 && decimalString.hasPrefix("1")

        if length == 0 || (length > 10 && !hasLeadingOne) || length > 11 {
            let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int

            return (newLength > 10) ? false : true
        }
        var index = 0 as Int
        let formattedString = NSMutableString()

        if hasLeadingOne {
            formattedString.append("1 ")
            index += 1
        }
        if (length - index) > 3 {
            let areaCode = decimalString.substring(with: NSMakeRange(index, 3))
            formattedString.appendFormat("(%@)", areaCode)
            index += 3
        }
        if length - index > 3 {
            let prefix = decimalString.substring(with: NSMakeRange(index, 3))
            formattedString.appendFormat("%@-", prefix)
            index += 3
        }

        let remainder = decimalString.substring(from: index)
        formattedString.append(remainder)
        textField.text = formattedString as String
        return false
    }
    else {
        return true
    }
}

Updated answer from Vikzilla for Swift 3:

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

    if textField == phoneTextField {

        let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
        let components = (newString as NSString).components(separatedBy: NSCharacterSet.decimalDigits.inverted)

        let decimalString = components.joined(separator: "") as NSString
        let length = decimalString.length
        let hasLeadingOne = length > 0 && decimalString.character(at: 0) == (1 as unichar)

        if length == 0 || (length > 10 && !hasLeadingOne) || length > 11 {
            let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int

            return (newLength > 10) ? false : true
        }
        var index = 0 as Int
        let formattedString = NSMutableString()

        if hasLeadingOne {
            formattedString.append("1 ")
            index += 1
        }
        if (length - index) > 3 {
            let areaCode = decimalString.substring(with: NSMakeRange(index, 3))
            formattedString.appendFormat("(%@)", areaCode)
            index += 3
        }
        if length - index > 3 {
            let prefix = decimalString.substring(with: NSMakeRange(index, 3))
            formattedString.appendFormat("%@-", prefix)
            index += 3
        }

        let remainder = decimalString.substring(from: index)
        formattedString.append(remainder)
        textField.text = formattedString as String
        return false

    } else {
        return true
    }
}

I've been struggling with this for a couple of hours, here's what I have:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
     {
    NSUInteger currentLength = textField.text.length;
    NSCharacterSet *numbers = [NSCharacterSet decimalDigitCharacterSet];        

    if (range.length == 1) {
        return YES;
    }


    if ([numbers characterIsMember:[string characterAtIndex:0]]) {


        if ( currentLength == 3 ) 
        {

            if (range.length != 1) 
            {

                NSString *firstThreeDigits = [textField.text substringWithRange:NSMakeRange(0, 3)];

                NSString *updatedText;

                if ([string isEqualToString:@"-"]) 
                {
                    updatedText = [NSString stringWithFormat:@"%@",firstThreeDigits];
                }

                else 
                {
                    updatedText = [NSString stringWithFormat:@"%@-",firstThreeDigits];
                }

                [textField setText:updatedText];
            }           
        }

        else if ( currentLength > 3 && currentLength < 8 ) 
        {

            if ( range.length != 1 ) 
            {

                NSString *firstThree = [textField.text substringWithRange:NSMakeRange(0, 3)];
                NSString *dash = [textField.text substringWithRange:NSMakeRange(3, 1)];

                NSUInteger newLenght = range.location - 4;

                NSString *nextDigits = [textField.text substringWithRange:NSMakeRange(4, newLenght)];

                NSString *updatedText = [NSString stringWithFormat:@"%@%@%@",firstThree,dash,nextDigits];

                [textField setText:updatedText];

            }

        }

        else if ( currentLength == 8 ) 
        {

            if ( range.length != 1 ) 
            {
                NSString *areaCode = [textField.text substringWithRange:NSMakeRange(0, 3)];

                NSString *firstThree = [textField.text substringWithRange:NSMakeRange(4, 3)];

                NSString *nextDigit = [textField.text substringWithRange:NSMakeRange(7, 1)];

                [textField setText:[NSString stringWithFormat:@"(%@) %@-%@",areaCode,firstThree,nextDigit]];
            }

        }
    }

    else {
        return NO;
    }

    return YES;
}

I hope someone can contribute.


Below function enforces (999)333-5555 format on the textField:

Swift 3:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if (textField == self.phone){
            let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
            let components = newString.components(separatedBy: NSCharacterSet.decimalDigits.inverted)

            let decimalString = components.joined(separator: "") as NSString
            let length = decimalString.length
            let hasLeadingOne = length > 0 && decimalString.character(at: 0) == (1 as unichar)

            if length == 0 || (length > 10 && !hasLeadingOne) || length > 11 {
                let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int

                return (newLength > 10) ? false : true
            }
            var index = 0 as Int
            let formattedString = NSMutableString()

            if hasLeadingOne {
                formattedString.append("1 ")
                index += 1
            }
            if (length - index) > 3 {
                let areaCode = decimalString.substring(with: NSMakeRange(index, 3))
                formattedString.appendFormat("(%@)", areaCode)
                index += 3
            }
            if length - index > 3 {
                let prefix = decimalString.substring(with: NSMakeRange(index, 3))
                formattedString.appendFormat("%@-", prefix)
                index += 3
            }

            let remainder = decimalString.substring(from: index)
            formattedString.append(remainder)
            textField.text = formattedString as String
            return false
        } else {
            return true
        }
    }