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





3 comments:

cDima said...

Hm, why the long name? Why not isFloat, isInt, isFloatInLocale?

BOOL isFloat = [@"12345.00" isFloat];

Jeff LaMarche said...

Good question. There's no reason why you couldn't change the name, but I didn't use that naming convention for two reasons:

1) There's the possibility of name conflicts if Apple adds a method with that name. Since they've used those semantics in other objects, it's possible they could add that functionality at some point to NSString.

2) It's not semantically accurate. A string that holds the value "$2,249.00" isn't, strictly speaking, a float. It can be parsed to one, but it isn't one.

I think #1 is more important. #2 is more of a fetish of mine in that I try to use names that are semantically accurate because it makes it easier for me to remember what they or what they do. With autocomplete, the longer name doens't add significant work. But, like I said, feel free to rename it or change it to suit yourself.

That's one thing about code - there really isn't a single right way to do anything. This name works for me, especially in the context where I used it (converting strings from SQLite into raw datatypes), but the name is not sacrosanct, by any means

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels