Thursday, November 6, 2008

Another Category on NSString

If you hadn't realized it by now, I love Objective-C's categories. They give us a perfect place to put re-usable logic that acts on delivered objects, and I wish more languages had a similar mechanism.

For SQLitePersistentObjects, I had to figure out in a few places if a value stored in an NSString was actually a floating point number, an integer, or a string, so I wrote a category to add a few methods to NSString. The first method tells you if a given string is holding a floating point number. It takes into account such things as the decimal separator, the grouping symbol, and the currency symbol based on the current user's language and region settings. So, for example, a string that held the value $123,456.00 would be identified as holding a valid floating point value (123456.00) on my system, but not on the system of somebody who lived in a country that used a comma as the decimal separator. You can call it like this:
BOOL isFloat = [@"12345.00" holdsFloatingPointValue];

If you needed to determine if it was a valid floating point number using a different locale, that's also an option with the second method. It works like this:
BOOL isFloatForLocale = [@"123456.00" holdsFloatingPointValueForLocale:theLocale];

The third method tells you if a string contains an integer value.
BOOL isInteger = [@"12345" holdsIntegerValue];

Nothing earth-shattering here, but might save you some time somewhere down the road. This should work equally well in Cocoa and Cocoa Touch.

Note: I could have, and probably should have used NSNumberFormatter to do some of the localized work more easily. For some reason, I didn't think NSNumberFormatter was available on the iPhone but it is, so I probably could have saved myself a few lines of code.


In NSString-NumberStuff.h:
//
// NSString-NumberStuff.h
// CashFlow
//
// Created by Jeff LaMarche on 11/6/08.

#import <Foundation/Foundation.h>

@interface NSString(NumberStuff)
- (BOOL)holdsFloatingPointValue;
- (BOOL)holdsFloatingPointValueForLocale:(NSLocale *)locale;
- (BOOL)holdsIntegerValue;
@end



In NSString-NumberStuff.m:
//
// NSString-NumberStuff.m
// CashFlow
//
// Created by Jeff LaMarche on 11/6/08.
// Copyright 2008 Jeff LaMarche Consulting. All rights reserved.
//

#import "NSString-NumberStuff.h"


@implementation NSString(NumberStuff)
- (BOOL)holdsFloatingPointValue
{
return [self holdsFloatingPointValueForLocale:[NSLocale currentLocale]];
}
- (BOOL)holdsFloatingPointValueForLocale:(NSLocale *)locale
{
NSString *currencySymbol = [locale objectForKey:NSLocaleCurrencySymbol];
NSString *decimalSeparator = [locale objectForKey:NSLocaleDecimalSeparator];
NSString *groupingSeparator = [locale objectForKey:NSLocaleGroupingSeparator];


// Must be at least one character
if ([self length] == 0)
return NO;
NSString *compare = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

// Strip out grouping separators
compare = [compare stringByReplacingOccurrencesOfString:groupingSeparator withString:@""];

// We'll allow a single dollar sign in the mix
if ([compare hasPrefix:currencySymbol])
{
compare = [compare substringFromIndex:1];
// could be spaces between dollar sign and first digit
compare = [compare stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}

NSUInteger numberOfSeparators = 0;

NSCharacterSet *validCharacters = [NSCharacterSet decimalDigitCharacterSet];
for (NSUInteger i = 0; i < [compare length]; i++)
{
unichar oneChar = [compare characterAtIndex:i];
if (oneChar == [decimalSeparator characterAtIndex:0])
numberOfSeparators++;
else if (![validCharacters characterIsMember:oneChar])
return NO;
}
return (numberOfSeparators == 1);

}
- (BOOL)holdsIntegerValue
{
if ([self length] == 0)
return NO;

NSString *compare = [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
NSCharacterSet *validCharacters = [NSCharacterSet decimalDigitCharacterSet];
for (NSUInteger i = 0; i < [compare length]; i++)
{
unichar oneChar = [compare characterAtIndex:i];
if (![validCharacters characterIsMember:oneChar])
return NO;
}
return YES;
}
@end

No comments:

Post a Comment