Remove all but numbers from NSString
Asked Answered
V

22

158

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?

Vargas answered 15/7, 2009 at 5:18 Comment(0)
D
379

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.

Devoirs answered 15/9, 2009 at 12:15 Comment(6)
Thanks! For other beginners, you can create your own custom NSCharacterSet by doing NSCharacterSet *myCharSet = [NSCharacterSet characterSetWithCharactersInString:@"charactersGoHere"]Rockingham
Great thank ! Just for my curiosity, do you have an idea why NSString *pureNumbers = [pureNumbers stringByTrimmingCharactersInSet: [NSCharacterSet decimalDigitCharacterSet] invertedSet] Is not working ?Giacopo
@Tommecpe stringByTrimmingCharactersInSet only removes from the beginning and end of the string, so it doesn't affect after the first non-matching character, or before the last non-matching character.Devoirs
i want to keep only numbers and alphabet, how can i do it?Uncharted
@Jacky in the example above you would replace [NSCharacterSet decimalDigitCharacterSet] with another that contains numbers and letters only. You can construct one by creating a NSMutableCharaterSet and passing a decimalDigitCharacterSet, uppercaseLetterCharacterSet and lowercaseLetterCharacterSet to formUnionWithCharacterSet:. Note that letterCharacterSet includes marks as well, hence the use of lower- and uppercase versions.Indene
Wow, this is extremely slow for some reason. Be careful if you're iterating through a data set.Moye
C
76

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.

Carree answered 15/7, 2009 at 7:47 Comment(12)
+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?)Inobservance
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.Gracegraceful
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.Roebuck
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.Carree
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 ;)Roebuck
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.Roebuck
don't forget that + is valid in a phone number!Extradite
See GregoryN's comment below for an important fix to the above code!Summary
That's waaaay to complicated.Pyro
Used your answer (most efficient) in a category to make it easier to implement - https://mcmap.net/q/150583/-remove-all-but-numbers-from-nsstringAcanthopterygian
This solution is overkill, you can do it in 1 string of code, and here it is implemented https://mcmap.net/q/150583/-remove-all-but-numbers-from-nsstringAbduce
This answer works when you wish to filter out all but an arbitrary set of characters. For example, I needed to filter extra characters out of a hex string e.g. "<ab73f109 e87700bc>". My characterSetWithCharactersInString is "0123456789ABCDEFabcdef".Loran
M
64

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

NSString *pureNumbers = [[phoneNumberString componentsSeparatedByCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] componentsJoinedByString:@""];
Momently answered 15/10, 2010 at 0:4 Comment(2)
The (currently) accepted answer is more or less identical to this one, but was posted 13 months earlier.Wellappointed
At the time I answered this, it didn't have this answer. Although it seems the current answer had already been proposed and I missed it: web.archive.org/web/20101115214033/http://stackoverflow.com/…Momently
P
30

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]];
    }
}
Pandorapandour answered 22/7, 2009 at 7:22 Comment(3)
I'm running 3.0 and this works for me. The more popular answer from Vries didn't work.Norword
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 youDurban
Only note that the '+' should be allowed character for the phone number.Warnock
B
27

Though this is an old question with working answers, I missed international format support. Based on the solution of simonobo, the altered character set includes a plus sign "+". International phone numbers are supported by this amendment as well.

NSString *condensedPhoneNumber = [[phoneNumber componentsSeparatedByCharactersInSet:
              [[NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]
              invertedSet]] 
              componentsJoinedByString:@""];

The Swift expressions are

var phoneNumber = " +1 (234) 567-1000 "
var allowedCharactersSet = NSMutableCharacterSet.decimalDigitCharacterSet()
allowedCharactersSet.addCharactersInString("+")
var condensedPhoneNumber = phoneNumber.componentsSeparatedByCharactersInSet(allowedCharactersSet.invertedSet).joinWithSeparator("")

Which yields +12345671000 as a common international phone number format.

Bifacial answered 24/2, 2012 at 11:38 Comment(3)
This is the best solution in the list, especially if you need the plus sign for international phone numbers.Griffith
For some reason, using an inverted character set scares me as far as performance goes. Does anyone happen to know if this is an unfounded fear?Raptorial
This one worked! Could you explain its working? @BifacialConnelly
J
11

Here is the Swift version of this.

import UIKit
import Foundation
var phoneNumber = " 1 (888) 555-5551    "
var strippedPhoneNumber = "".join(phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))
Josephjosepha answered 26/12, 2014 at 16:35 Comment(1)
Swift 2.0: phoneNumber.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")Knavish
C
11

Swift version of the most popular answer:

var newString = join("", oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet))

Edit: Syntax for Swift 2

let newString = oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")

Edit: Syntax for Swift 3

let newString = oldString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
Corner answered 14/7, 2015 at 22:25 Comment(1)
is there a way to keep the decimal separated symbol? The dot (or comma) as a function of the default settings of the device? Your solution eliminate everything but numbersIllusionism
I
5

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"
Inexistent answered 24/11, 2009 at 11:15 Comment(0)
I
4

It Accept only mobile number

NSString * strippedNumber = [mobileNumber stringByReplacingOccurrencesOfString:@"[^0-9]" withString:@"" options:NSRegularExpressionSearch range:NSMakeRange(0, [mobileNumber length])];
Impassible answered 2/12, 2014 at 10:18 Comment(0)
I
3

It might be worth noting that the accepted componentsSeparatedByCharactersInSet: and componentsJoinedByString:-based answer is not a memory-efficient solution. It allocates memory for the character set, for an array and for a new string. Even if these are only temporary allocations, processing lots of strings this way can quickly fill the memory.

A memory friendlier approach would be to operate on a mutable copy of the string in place. In a category over NSString:

-(NSString *)stringWithNonDigitsRemoved {
    static NSCharacterSet *decimalDigits;
    if (!decimalDigits) {
        decimalDigits = [NSCharacterSet decimalDigitCharacterSet];
    }
    NSMutableString *stringWithNonDigitsRemoved = [self mutableCopy];
    for (CFIndex index = 0; index < stringWithNonDigitsRemoved.length; ++index) {
        unichar c = [stringWithNonDigitsRemoved characterAtIndex: index];
        if (![decimalDigits characterIsMember: c]) {
            [stringWithNonDigitsRemoved deleteCharactersInRange: NSMakeRange(index, 1)];
            index -= 1;
        }
    }
    return [stringWithNonDigitsRemoved copy];
}

Profiling the two approaches have shown this using about 2/3 less memory.

Indene answered 17/9, 2014 at 17:26 Comment(0)
P
2

You can use regular expression on mutable string:

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
                                @"[^\\d]"
                                options:0
                                error:nil];

[regex replaceMatchesInString:str
                      options:0 
                        range:NSMakeRange(0, str.length) 
                 withTemplate:@""];
Photooffset answered 17/6, 2014 at 8:4 Comment(0)
A
1

Built the top solution as a category to help with broader problems:

Interface:

@interface NSString (easyReplace)
- (NSString *)stringByReplacingCharactersNotInSet:(NSCharacterSet *)set 
                                             with:(NSString *)string;
@end

Implemenation:

@implementation NSString (easyReplace)
- (NSString *)stringByReplacingCharactersNotInSet:(NSCharacterSet *)set 
                                             with:(NSString *)string
{
    NSMutableString *strippedString = [NSMutableString
                                       stringWithCapacity:self.length];

    NSScanner *scanner = [NSScanner scannerWithString:self];

    while ([scanner isAtEnd] == NO) {
        NSString *buffer;
        if ([scanner scanCharactersFromSet:set intoString:&buffer]) {
            [strippedString appendString:buffer];
        } else {
            [scanner setScanLocation:([scanner scanLocation] + 1)];
            [strippedString appendString:string];
        }
    }
    return [NSString stringWithString:strippedString];
}
@end

Usage:

NSString *strippedString = 
 [originalString stringByReplacingCharactersNotInSet:
   [NSCharacterSet setWithCharactersInString:@"01234567890" 
                                        with:@""];
Acanthopterygian answered 3/7, 2013 at 21:50 Comment(0)
G
1

Swift 3

let notNumberCharacters = NSCharacterSet.decimalDigits.inverted
let intString = yourString.trimmingCharacters(in: notNumberCharacters)
Gabfest answered 15/11, 2016 at 21:9 Comment(1)
This will only trim non digits characters from the beginning and the end.Lavern
G
1

swift 4.1

var str = "75003 Paris, France"
var stringWithoutDigit = (str.components(separatedBy:CharacterSet.decimalDigits)).joined(separator: "")
print(stringWithoutDigit)
Gayegayel answered 14/8, 2018 at 17:47 Comment(0)
I
0

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 for tips on doing so. Remember that phone numbers are formatted differently depending on location and/or locale.

Inobservance answered 15/7, 2009 at 5:36 Comment(3)
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.Gracegraceful
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.Carree
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.Inobservance
R
0

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.

Roebuck answered 22/7, 2009 at 13:24 Comment(0)
N
0

For those searching for phone extraction, you can extract the phone numbers from a text using NSDataDetector, for example:

NSString *userBody = @"This is a text with 30612312232 my phone";
if (userBody != nil) {
    NSError *error = NULL;
    NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypePhoneNumber error:&error];
    NSArray *matches = [detector matchesInString:userBody options:0 range:NSMakeRange(0, [userBody length])];
    if (matches != nil) {
        for (NSTextCheckingResult *match in matches) {
            if ([match resultType] == NSTextCheckingTypePhoneNumber) {
                DbgLog(@"Found phone number %@", [match phoneNumber]);
            }
        }
    }
}

`

Nostoc answered 14/4, 2012 at 22:44 Comment(0)
C
0

I created a category on NSString to simplify this common operation.

NSString+AllowCharactersInSet.h

@interface NSString (AllowCharactersInSet)

- (NSString *)stringByAllowingOnlyCharactersInSet:(NSCharacterSet *)characterSet;

@end

NSString+AllowCharactersInSet.m

@implementation NSString (AllowCharactersInSet)

- (NSString *)stringByAllowingOnlyCharactersInSet:(NSCharacterSet *)characterSet {
    NSMutableString *strippedString = [NSMutableString
                                   stringWithCapacity:self.length];

    NSScanner *scanner = [NSScanner scannerWithString:self];

    while (!scanner.isAtEnd) {
        NSString *buffer = nil;

        if ([scanner scanCharactersFromSet:characterSet intoString:&buffer]) {
            [strippedString appendString:buffer];
        } else {
            scanner.scanLocation = scanner.scanLocation + 1;
        }
    }

    return strippedString;
}

@end
Cachalot answered 5/9, 2012 at 0:35 Comment(0)
T
0

I think currently best way is:

phoneNumber.replacingOccurrences(of: "\\D",
                               with: "",
                            options: String.CompareOptions.regularExpression)
Travax answered 17/4, 2017 at 11:31 Comment(0)
A
0

Swift 5

let newString = origString.components(separatedBy: CharacterSet.decimalDigits.inverted).joined(separator: "")
Amylase answered 21/6, 2019 at 4:54 Comment(0)
S
-1

Based on Jon Vogel's answer here it is as a Swift String extension along with some basic tests.

import Foundation
extension String {
    func stringByRemovingNonNumericCharacters() -> String {
        return self.componentsSeparatedByCharactersInSet(NSCharacterSet.decimalDigitCharacterSet().invertedSet).joinWithSeparator("")
    }
}

And some tests proving at least basic functionality:

import XCTest

class StringExtensionTests: XCTestCase {

    func testStringByRemovingNonNumericCharacters() {

        let baseString = "123"
        var testString = baseString
        var newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == testString)

        testString = "a123b"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == baseString)

        testString = "a=1-2_3@b"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == baseString)

        testString = "(999) 999-9999"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString.characters.count == 10)
        XCTAssertTrue(newString == "9999999999")

        testString = "abc"
        newString = testString.stringByRemovingNonNumericCharacters()
        XCTAssertTrue(newString == "")
    }
}

This answers the OP's question but it could be easily modified to leave in phone number related characters like ",;*#+"

Strontianite answered 23/4, 2016 at 15:36 Comment(0)
P
-4
NSString *originalPhoneNumber = @"(123) 123-456 abc";
NSCharacterSet *numbers = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *trimmedPhoneNumber = [originalPhoneNumber stringByTrimmingCharactersInSet:numbers];

];

Keep it simple!

Pyro answered 20/8, 2010 at 20:0 Comment(1)
this will only trim those characters from the beginning and end.Chastity

© 2022 - 2024 — McMap. All rights reserved.