Check if an NSString contains a valid number but localized

How to check if a string contains a valid number considering localized number formats.

This question is not primary about converting a number. I can do that with NSNumberFormatter. And if I can't then I don't even need to do it and can leave the string as string. But I need to check whether it contains a valid numer.

BTW, we are in the middle of a textField:shouldChangeCharactersInRange: ... delegate method. Here I want to prevent keying in illegal characters by returning NO.

This is the code that I have:

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

    // Only test for numbers if it is a certain text field. 
    if (textField == self.numValueTextField) {
        NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];

        // The user deleting all input is perfectly acceptable.
        if ([resultingString length] == 0) {
            return true;
        }

        double holder;

        NSScanner *scan = [NSScanner scannerWithString: resultingString];

        BOOL isNumeric = [scan scanDouble: &holder] && [scan isAtEnd];

        if (isNumeric) {

            [self.detailItem setValue:number forKey:kValueDouble];
        }

        return isNumeric;
    }
    return YES;  // default for any other text field - if any.
}

That works fine but it implies English notations. Meaning the floating point must be a pont. But it could be a comma or whatever in certain parts of the world.

I do know how to check for certain characters. So I could check for 0-9, comma and point. But is there a 'proper' way of doing that?

If it is of importance: I am in an iOS environment.


A number formatter will use the current locale's information by default, so it should be able to do this checking for you. Just try to format the string. If the result is nil , then it's not valid.

static NSNumberFormatter * formatter = [NSNumberFormatter new];
//... build string
NSNumber * num = [formatter numberFromString:resultingString];
BOOL isNumeric = (num != nil);

For example:

NSNumberFormatter * formatter = [NSNumberFormatter new];

// US uses period as decimal separator
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];

NSLog(@"%@", [formatter numberFromString:@"3.1415"]);   // Valid NSNumber
NSLog(@"%@", [formatter numberFromString:@"3.14.15"]);  // nil
NSLog(@"%@", [formatter numberFromString:@"31415"]);    // Valid
NSLog(@"%@", [formatter numberFromString:@"3,1415"]);   // nil

// Italy uses a comma as decimal separator
[formatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"it"]];
NSLog(@"%@", [formatter numberFromString:@"3.1415"]);    // nil
NSLog(@"%@", [formatter numberFromString:@"3.14.15"]);   // nil
NSLog(@"%@", [formatter numberFromString:@"31415"]);     // Valid
NSLog(@"%@", [formatter numberFromString:@"3,1415"]);    // Valid

Or you might prefer to use getObjectValue:forString:range:error: , which will simply return YES or NO to indicate success at parsing, and also give you an error object if you're interested in further details.


This is how I do it. The key piece of information you want is [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator] , which will tell you the current decimal separator. Then you look for any characters that aren't in the legal set.

- (BOOL)isLegalDigitString:(NSString *)string
              forTextField:(UITextField *)textField
           hasDecimalPoint:(BOOL)hasDecimalPoint
{
  // It's always legal to delete
  if ([string length] == 0)
  {
    return YES;
  }

  // Only legal characters
  NSString *decimalSeparator = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];

  NSString *legalCharacters = [@"1234567890" stringByAppendingString:
                               (hasDecimalPoint ? decimalSeparator
                                : @"")];
  NSCharacterSet *forbiddenCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:legalCharacters] invertedSet];
  if ([string rangeOfCharacterFromSet:forbiddenCharacterSet].location != NSNotFound)
  {
    return NO;
  }

  // Only one decimal point
  if (hasDecimalPoint &&
      [string rangeOfString:decimalSeparator].location != NSNotFound &&
      [[textField text] rangeOfString:decimalSeparator].location != NSNotFound)
  {
    return NO;
  }

  return YES;
}

As an example of how I use this:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
  if (textField == [self integerTextField])
  {
    return [self isLegalDigitString:string forTextField:textField hasDecimalPoint:NO];
  }
  else if (textField == [self floatTextField])
  {
    return [self isLegalDigitString:string forTextField:textField hasDecimalPoint:YES];
  }

  return YES;
}

The one thing this doesn't do really well is manage limited precision (a field that can only have tenths for instance). It would need some redesign for that.


You can use NSLocale to get the localized grouping separator and then strip it out of your strings since NSScanner doesn't handle grouping.

NSString *sep = [[NSLocale currentLocale] objectForKey:NSLocaleGroupingSeparator];
NSString *string2 = [string1 stringByReplacingOccurrencesOfString:sep withString:@""];

Initialize an NSScanner that takes into account localized decimal separators.

NSScanner *scan = [NSScanner localizedScannerWithString:string2];

Then proceed with your NSScanner code as it is. Using NSScanner is more robust than the hand coded methods because it handles all IEEE compliant numbers including +/- and scientific notation. It's also shorter.

链接地址: http://www.djcxy.com/p/67358.html

上一篇: iOS获取基于区域的数字描述

下一篇: 检查NSString是否包含有效的数字,但是是本地化的