i think this will return a as 0.0 if the string is invalid (it might throw an exception, in which case you can just catch it, the docs say 0.0 tho). more info here
Use NSNumberFormatter's numberFromString: method. This will return an NSNumber if it can parse the string correctly, or nil if it cannot.
Use NSScanner
Strip any non-numeric character and see if the string still matches
Use a regular expression
IMO, using something like -[NSString doubleValue] wouldn't be the best option because both @"0.0" and @"abc" will have a doubleValue of 0. The *value methods all return 0 if they're not able to convert the string properly, so it would be difficult to distinguish between a legitimate string of @"0" and a non-valid string. Something like C's strtol function would have the same issue.
I think using NSNumberFormatter would be the best option, since it takes locale into account (ie, the number @"1,23" in Europe, versus @"1.23" in the USA).
I use this code in my Mac app, the same or similar should work with the iPhone. It's based on the RegexKitLite regular expressions and turns the text red when its invalid.
static bool TextIsValidValue( NSString* newText, double &value )
{
bool result = false;
if ( [newText isMatchedByRegex:@"^(?:|0|[1-9]\\d*)(?:\\.\\d*)?$"] ) {
result = true;
value = [newText doubleValue];
}
return result;
}
- (IBAction) doTextChanged:(id)sender;
{
double value;
if ( TextIsValidValue( [i_pause stringValue], value ) ) {
[i_pause setTextColor:[NSColor blackColor]];
// do something with the value
} else {
[i_pause setTextColor:[NSColor redColor]];
}
}
If you want a user to only be allowed to enter numerals, you can make your ViewController implement part of UITextFieldDelegate and define this method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];
// The user deleting all input is perfectly acceptable.
if ([resultingString length] == 0) {
return true;
}
NSInteger holder;
NSScanner *scan = [NSScanner scannerWithString: resultingString];
return [scan scanInteger: &holder] && [scan isAtEnd];
}
There are probably more efficient ways, but I find this a pretty convenient way. And the method should be readily adaptable to validating doubles or whatever: just use scanDouble: or similar.
-- this is for validating input is numeric chars only. Look at the documentation for NSCharacterSet for the other options. You can use characterSetWithCharactersInString to specify any set of valid input characters.
I wanted a text field that only allowed integers. Here's what I ended up with (using info from here and elsewhere):
Create integer number formatter (in UIApplicationDelegate so it can be reused):
@property (nonatomic, retain) NSNumberFormatter *integerNumberFormatter;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Create and configure an NSNumberFormatter for integers
integerNumberFormatter = [[NSNumberFormatter alloc] init];
[integerNumberFormatter setMaximumFractionDigits:0];
return YES;
}
Use filter in UITextFieldDelegate:
@interface MyTableViewController : UITableViewController <UITextFieldDelegate> {
ictAppDelegate *appDelegate;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// Make sure the proposed string is a number
NSNumberFormatter *inf = [appDelegate integerNumberFormatter];
NSString* proposedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
NSNumber *proposedNumber = [inf numberFromString:proposedString];
if (proposedNumber) {
// Make sure the proposed number is an integer
NSString *integerString = [inf stringFromNumber:proposedNumber];
if ([integerString isEqualToString:proposedString]) {
// proposed string is an integer
return YES;
}
}
// Warn the user we're rejecting the change
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
return NO;
}
Hi had the exact same problem and I don't see the answer I used posted, so here it is.
I created and connected my text field via IB. When I connected it to my code via Control+Drag, I chose Action, then selected the Editing Changed event. This triggers the method on each character entry. You can use a different event to suit.
Afterwards, I used this simple code to replace the text. Note that I created my own character set to include the decimal/period character and numbers. Basically separates the string on the invalid characters, then rejoins them with empty string.
Old thread, but it's worth mentioning that Apple introduced NSRegularExpression in iOS 4.0. (Taking the regular expression from Peter's response)
// Look for 0-n digits from start to finish
NSRegularExpression *noFunnyStuff = [NSRegularExpression regularExpressionWithPattern:@"^(?:|0|[1-9]\\d*)(?:\\.\\d*)?$" options:0 error:nil];
// There should be just one match
if ([noFunnyStuff numberOfMatchesInString:<#theString#> options:0 range:NSMakeRange(0, <#theString#>.length)] == 1)
{
// Yay, digits!
}
I suggest storing the NSRegularExpression instance somewhere.
Late to the game but here a handy little category I use that accounts for decimal places and the local symbol used for it. link to its gist here
@interface NSString (Extension)
- (BOOL) isAnEmail;
- (BOOL) isNumeric;
@end
@implementation NSString (Extension)
/**
* Determines if the current string is a valid email address.
*
* @return BOOL - True if the string is a valid email address.
*/
- (BOOL) isAnEmail
{
NSString *emailRegex = @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
NSPredicate *emailTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", emailRegex];
return [emailTest evaluateWithObject:self];
}
/**
* Determines if the current NSString is numeric or not. It also accounts for the localised (Germany for example use "," instead of ".") decimal point and includes these as a valid number.
*
* @return BOOL - True if the string is numeric.
*/
- (BOOL) isNumeric
{
NSString *localDecimalSymbol = [[NSLocale currentLocale] objectForKey:NSLocaleDecimalSeparator];
NSMutableCharacterSet *decimalCharacterSet = [NSMutableCharacterSet characterSetWithCharactersInString:localDecimalSymbol];
[decimalCharacterSet formUnionWithCharacterSet:[NSCharacterSet alphanumericCharacterSet]];
NSCharacterSet* nonNumbers = [decimalCharacterSet invertedSet];
NSRange r = [self rangeOfCharacterFromSet: nonNumbers];
if (r.location == NSNotFound)
{
// check to see how many times the decimal symbol appears in the string. It should only appear once for the number to be numeric.
int numberOfOccurances = [[self componentsSeparatedByString:localDecimalSymbol] count]-1;
return (numberOfOccurances > 1) ? NO : YES;
}
else return NO;
}
@end
IMO the best way to accomplish your goal is to display a numeric keyboard rather than the normal keyboard. This restricts which keys are available to the user. This alleviates the need to do validation, and more importantly it prevents the user from making a mistake. The number pad is also much nicer for entering numbers because the keys are substantially larger.
In interface builder select the UITextField, go to the Attributes Inspector and change the "Keyboard Type" to "Decimal Pad".
That'll make the keyboard look like this:
The only thing left to do is ensure the user doesn't enter in two decimal places. You can do this while they're editing. Add the following code to your view controller. This code removes a second decimal place as soon as it is entered. It appears to the user as if the 2nd decimal never appeared in the first place.
let formatString = "12345"
if let number = Decimal(string:formatString){
print("String contains only number")
}
else{
print("String doesn't contains only number")
}
Implement the following method and update the "decsAllowed" constant with the desired amount of decimals or 0 if you want a natural number.
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let decsAllowed: Int = 2
let candidateText = NSString(string: textField.text!).replacingCharacters(in: range, with: string)
let decSeparator: String = NumberFormatter().decimalSeparator!;
let splitted = candidateText.components(separatedBy: decSeparator)
let decSeparatorsFound = splitted.count - 1
let decimalPart = decSeparatorsFound > 0 ? splitted.last! : ""
let decimalPartCount = decimalPart.characters.count
let characterSet = NSMutableCharacterSet.decimalDigit()
if decsAllowed > 0 {characterSet.addCharacters(in: decSeparator)}
let valid = characterSet.isSuperset(of: CharacterSet(charactersIn: candidateText)) &&
decSeparatorsFound <= 1 &&
decsAllowed >= decimalPartCount
return valid
}
If afterwards you need to safely convert that string into a number, you can just use Double(yourstring) or Int(yourstring) type cast, or the more academic way:
let formatter = NumberFormatter()
let theNumber: NSNumber = formatter.number(from: yourTextField.text)!