views:

2169

answers:

8

I have an NSString (phone number) with some parenthesis and hyphens as some phone numbers are formatted. How would I remove all characters except numbers from the string?

A: 

If you're just looking to grab the numbers from the string, you could certainly use regular expressions to parse them out. For doing regex in Objective-C, check out RegexKit. Edit: As @Nathan points out, using NSScanner is a much simpler way to parse all numbers from a string. I totally wasn't aware of that option, so props to him for suggesting it. (I don't even like using regex myself, so I prefer approaches that don't require them.)

If you want to format phone numbers for display, it's worth taking a look at NSNumberFormatter. I suggest you read through this related SO question and this iPhone-specific tutorial for tips on doing so. Remember that phone numbers are formatted differently depending on location and/or locale.

Quinn Taylor
Oh the painful hours I've spent developing good phone number formatters and parsers. The linked threads are a good start, but the general case of formatting global phone numbers for display is a long road, and as noted in the linked threads, Apple doesn't give you any access to the Address Book phone number formatters, and are very inconsistent in how phone numbers are presented from the Address Book API. The only thing harder than formatting a phone number for display is determining if two phone numbers are equal. At least the OP's question is the easiest of the problems.
Rob Napier
I believe that those links to formatting phone numbers are misleading unless you're happy with a primitive, US-centric implementation.In lieu of a proper localized phone number formatter from Apple, the only way of doing this properly is to copy the formatting templates off the device (UIPhoneFormats.plist in OS 2.x) and reproduce the templates yourself depending on the users' locale. This is a non-trivial task.
Nathan de Vries
That's why I mentioned the localization of number formatters. I didn't pretend to post any form of a complete solution for that — it's a much longer discussion and would make more sense to make it a separate SO question.
Quinn Taylor
+10  A: 

There's no need to use a regular expressions library as the other answers suggest -- the class you're after is called NSScanner. It's used as follows:

NSString *originalString = @"(123) 123123 abc";
NSMutableString *strippedString = [NSMutableString 
        stringWithCapacity:originalString.length];

NSScanner *scanner = [NSScanner scannerWithString:originalString];
NSCharacterSet *numbers = [NSCharacterSet 
        characterSetWithCharactersInString:@"0123456789"];

while ([scanner isAtEnd] == NO) {
  NSString *buffer;
  if ([scanner scanCharactersFromSet:numbers intoString:&buffer]) {
    [strippedString appendString:buffer];

  } else {
    [scanner setScanLocation:([scanner scanLocation] + 1)];
  }
}

NSLog(@"%@", strippedString); // "123123123"

EDIT: I've updated the code because the original was written off the top of my head and I figured it would be enough to point the people in the right direction. It seems that people are after code they can just copy-paste straight into their application.

I also agree that Michael Pelz-Sherman's solution is more appropriate than using NSScanner, so you might want to take a look at that.

Nathan de Vries
+1 Good answer that directly addresses the question. I've edited my answer to advocate this approach, but I'm leaving the second half as-is, since it is still helpful) which addresses the flip-side problem of formatting a phone number for display. (Next, can you leave a constructive comment when downvoting, if only for later readers?)
Quinn Taylor
It may be handy to know that there is a +decimalDigitCharacterSet method of NSCharacterSet that will give you all the decimal digits. This is slightly different than the set Nathan lists, because it includes all symbols that represent decimal numbers including, for instance, Arabic-Indic digits (١٢٣٤٥ etc). Depending on your application, that could occasionally be a problem, but generally its either good or neutral, and a little shorter to type.
Rob Napier
I'm pretty sure that this answer doesn't actually work, and isn't really the right approach to the problem. If you actually try the code as shown (first adding an @ before the first param to NSLog, making it an objc string), you'll find that it either prints <null> or crashes. Why? See my answer below.
Jack Nutting
No need for another answer -- that's what comments are for. I've updated the solution, including a reference to Michael Pelz-Sherman's solution.
Nathan de Vries
Thanks, Nathan! I do understand comments vs answers; I started writing a comment, but I got too long-winded and couldn't fit it in 600 chars ;)
Jack Nutting
Oh, and I hate to be a party pooper, but your updated answer doesn't work either; you're never updating the scanLocation after a failed scan, so it infinite-loops, scanning the first character forever.
Jack Nutting
don't forget that + is valid in a phone number!
hop
See GregoryN's comment below for an important fix to the above code!
Josh Vickery
That's waaaay to complicated.
+6  A: 

Hi,

This is great, but the code does not work for me on the iPhone 3.0 SDK.

If I define strippedString as you show here, I get a BAD ACCESS error when trying to print it after the scanCharactersFromSet:intoString call.

If I do it like so:

NSMutableString *strippedString = [NSMutableString stringWithCapacity:10];

I end up with an empty string, but the code doesn't crash.

I had to resort to good old C instead:

for (int i=0; i<[phoneNumber length]; i++) {
 if (isdigit([phoneNumber characterAtIndex:i])) {
  [strippedString appendFormat:@"%c",[phoneNumber characterAtIndex:i]];
 }
}

Any idea why NSScanner let me down?

Michael Pelz-Sherman
I'm running 3.0 and this works for me. The more popular answer from Vries didn't work.
Neo42
The number one answer will not work for me. The scanner stops once it reaches a ( ) or -This answer works great!! Good old C!! Thank you
Jeff
A: 

Um. The first answer seems totally wrong to me. NSScanner is really meant for parsing. Unlike regex, it has you parsing the string one tiny chunk at a time. You initialize it with a string, and it maintains an index of how far along the string it's gotten; That index is always its reference point, and any commands you give it are relative to that point. You tell it, "ok, give me the next chunk of characters in this set" or "give me the integer you find in the string", and those start at the current index, and move forward until they find something that doesn't match. If the very first character already doesn't match, then the method returns NO, and the index doesn't increment.

The code in the first example is scanning "(123)456-7890" for decimal characters, which already fails from the very first character, so the call to scanCharactersFromSet:intoString: leaves the passed-in strippedString alone, and returns NO; The code totally ignores checking the return value, leaving the strippedString unassigned. Even if the first character were a digit, that code would fail, since it would only return the digits it finds up until the first dash or paren or whatever.

If you really wanted to use NSScanner, you could put something like that in a loop, and keep checking for a NO return value, and if you get that you can increment the scanLocation and scan again; and you also have to check isAtEnd, and yada yada yada. In short, wrong tool for the job. Michael's solution is better.

Jack Nutting
+3  A: 

Old question, but how about:

  NSString *newString = [[origString componentsSeparatedByCharactersInSet:
             [[NSCharacterSet decimalDigitCharacterSet] invertedSet]] 
             componentsJoinedByString:@""];

It explodes the source string on the set of non-digits, then reassembles them using an empty string separator. Not as efficient as picking through characters, but much more compact in code.

simonobo
Nice example. Super simple and compact!
stitz
+2  A: 

Thanks for the example. It has only one thing missing the increment of the scanLocation in case one of the characters in originalString is not found inside the numbers CharacterSet object. I have added an else {} statement to fix this.

NSString *originalString = @"(123) 123123 abc";
NSMutableString *strippedString = [NSMutableString 
        stringWithCapacity:originalString.length];

NSScanner *scanner = [NSScanner scannerWithString:originalString];
NSCharacterSet *numbers = [NSCharacterSet 
        characterSetWithCharactersInString:@"0123456789"];

while ([scanner isAtEnd] == NO) {
  NSString *buffer;
  if ([scanner scanCharactersFromSet:numbers intoString:&buffer]) {
    [strippedString appendString:buffer];
  }
  // --------- Add the following to get out of endless loop
  else {
     [scanner setScanLocation:([scanner scanLocation] + 1)];
  }    
  // --------- End of addition
}

NSLog(@"%@", strippedString); // "123123123"
GregoryN
A: 
NSString *originalPhoneNumber = @"(123) 123-456 abc";
NSCharacterSet *numbers = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *trimmedPhoneNumber = [originalPhoneNumber stringByTrimmingCharactersInSet:numbers];

];

Keep it simple!

this will only trim those characters from the beginning and end.
raidfive
+1  A: 

The accepted answer is overkill for what is being asked. This is much simpler:

NSString *pureNumbers = [[phoneNumberString componentsSeparatedByCharactersInSet:[[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet]] componentsJoinedByString:@""];
Yacine Filali