Check if NSString contains a valid number but is localized

How to check if a string contains a valid number, given the formats of localized numbers.

This question is not a primary one regarding number conversion. I can do this with NSNumberFormatter. And if I can’t, then I don’t even need to do this, and I can leave the line as a line. But I need to check if it contains a valid number.

By the way, we are in the middle of the textField: shouldChangeCharactersInRange: ... delegate method. Here I want to prevent the input of invalid characters by returning NO.

This is the code 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. } 

This works great, but it means English notation. Floating point value must be pont. But it can be a comma or something in some parts of the world.

I know how to test certain characters. So I could check 0-9, comma and period. But is there a “right” way to do this?

If this is important: I'm on iOS.

+6
source share
4 answers

The formatter number will use the current local local information by default, so it should be able to perform this check for you. Just try formatting the string. If the result is nil , then it is invalid.

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

For instance:

 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 can use getObjectValue:forString:range:error: which will simply return YES or NO to indicate parsing success, and also give you an error object if you are interested in additional details.

+7
source

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

 - (BOOL)isLegalDigitString:(NSString *)string forTextField:(UITextField *)textField hasDecimalPoint:(BOOL)hasDecimalPoint { // It 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 only thing that doesn’t work very well is to limit the accuracy (a field that can only have tenths). This will require some kind of redesign.

+2
source

You can use NSLocale to get a localized grouping delimiter, and then remove it from your lines, because NSScanner does not handle grouping.

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

Initialize an NSScanner that considers localized decimal separators.

 NSScanner *scan = [NSScanner localizedScannerWithString:string2]; 

Then execute your NSScanner code as is. Using NSScanner is more reliable than manual coded methods, as it handles all IEEE-compatible numbers, including +/- and scientific notation. It is also shorter.

0
source

Honestly, any valid number test that should work even for partial numbers is very difficult to implement.

  • Do not try to override checks already implemented in NSNumberFormatter . Different languages ​​use different decimal separators, but also different digits (EDIT: unfortunately, NSNumberFormatter cannot check non-Arabic digits).

  • There are many special cases that are not valid numbers, but can become real numbers, for example. empty string, minus sign, decimal separator, minus sign combined with decimal separator. If you admit scientific notation, it will become much more complicated.

The simplest solution is to check only a finite number. Check the input when the user confirms (input taps, taps outside the input view).

However, also note that NSNumberFromatter can actually check partial strings. For more information, see His superclass NSFormatter .

0
source

All Articles