layout | title | framework | rating | description |
---|---|---|---|---|
post |
NSFormatter |
Foundation |
8.0 |
Conversion is the tireless errand of software development. Most programming tasks boil down to some variation of transforming data into something more useful. |
Conversion is the tireless errand of software development. Most programming tasks boil down to some variation of transforming data into something more useful.
In the case of user-facing software, converting data into human-readable form is an essential task, and a complex one at that. A user's preferred language, locale, calendar, or currency can all factor into how information should be displayed, as can other constraints, such as a label's dimensions.
All of this is to say that sending -description
to an object just isn't going to cut it in most circumstances. Even +stringWithFormat:
is going to ultimately disappoint. No, the real tool for this job is NSFormatter
.
NSFormatter
is an abstract class for transforming data into a textual representation. It can also interpret valid textual representations back into data.
Its origins trace back to NSCell
, which is used to display information and accept user input in tables, form fields, and other views in AppKit. Much of the API design of NSFormatter reflects this.
Foundation provides two concrete subclasses for NSFormatter
: NSNumberFormatter
and NSDateFormatter
. As some of the oldest members of the Foundation framework, these classes are astonishingly well-suited to their respective domains, in that way only decade-old software can.
NSNumberFormatter
handles every aspect of number formatting imaginable, from mathematical and scientific notation, to currencies and percentages. Nearly everything about the formatter can be customized, whether it's the currency symbol, grouping separator, number of significant digits, rounding behavior, fractions, character for infinity, string representation for 0
, or maximum / minimum values. It can even write out numbers in several languages!
When using an NSNumberFormatter
, the first order of business is to determine what kind of information you're displaying. Is it a price? Is this a whole number, or should decimal values be shown?
NSNumberFormatter
can be configured for any one of the following formats, with -setNumberStyle:
:
To illustrate the differences between each style, here is how the number 12345.6789
would be displayed for each:
NSNumberFormatterNoStyle
: 12346NSNumberFormatterDecimalStyle
: 12345.6789NSNumberFormatterCurrencyStyle
: $12345.68NSNumberFormatterPercentStyle
: 1234567%NSNumberFormatterScientificStyle
: 1.23456789E4NSNumberFormatterSpellOutStyle
: twelve thousand three hundred forty-five point six seven eight nine
By default, NSNumberFormatter
will format according to the current locale settings, for things like currency symbol ($, £, €, etc.) and whether to use "," or "." as the decimal separator.
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
for (NSString *identifier in @[@"en_US", @"fr_FR"]) {
numberFormatter.locale = [NSLocale localeWithLocaleIdentifier:identifier];
NSLog(@"%@: %@", identifier, [numberFormatter stringFromNumber:@(1234.5678)]);
}
en_US: $1,234.57
fr_FR: 1 234,57 €
All of those settings can be overridden on an individual basis, but for most apps, the best strategy would be deferring to the locale's default settings.
In order to prevent numbers from getting annoyingly pedantic ("thirty-two point three three, repeating, of course..."), make sure to get a handle on NSNumberFormatter
's rounding behavior.
The easiest way to do this, would be to setUsesSignificantDigits:
to YES
, and then set minimum and maximum number of significant digits appropriately. For example, a number formatter used for approximate distances in directions, would do well with significant digits to the tenths place for miles or kilometers, but only the ones place for feet or meters.
For anything more advanced, an NSDecimalNumberHandler
object can be passed as the roundingBehavior
property of a number formatter.
NSDateFormatter
is the be all and end all of getting textual representations of both dates and times.
The most important properties for an NSDateFormatter
object are its dateStyle
and timeStyle
. Like -[NSNumberFormatter numberStyle]
, these styles provide common preset configurations for common formats. In this case, the various formats are distinguished by their specificity (more specific = longer).
Both properties share a single set of enum
values:
Style | Description | Examples | |
---|---|---|---|
Date | Time | ||
NSDateFormatterNoStyle | Specifies no style. | ||
NSDateFormatterShortStyle | Specifies a short style, typically numeric only. | 11/23/37 | 3:30pm |
NSDateFormatterMediumStyle | Specifies a medium style, typically with abbreviated text. | Nov 23, 1937 | 3:30:32pm |
NSDateFormatterLongStyle | Specifies a long style, typically with full text. | November 23, 1937 | 3:30:32pm |
NSDateFormatterFullStyle | Specifies a full style with complete details. | Tuesday, April 12, 1952 AD | 3:30:42pm PST |
dateStyle
and timeStyle
are set independently. For example to display just the time, an NSDateFormatter
would be configured with a dateStyle
of NSDateFormatterNoStyle
:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterNoStyle];
[formatter setTimeStyle:NSDateFormatterMediumStyle];
NSLog(@"%@", [formatter stringFromDate:[NSDate date]]);
// 12:11:19pm
Whereas setting both to NSDateFormatterLongStyle
yields the following:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateStyle:NSDateFormatterLongStyle];
[formatter setTimeStyle:NSDateFormatterLongStyle];
NSLog(@"%@", [formatter stringFromDate:[NSDate date]]);
// Monday, November 11, 2013 12:11:19pm PST
```
As you might expect, each aspect of the date format can alternatively be configured individually, a la carte. For any aspiring time wizards `NSDateFormatter` has a bevy of different knobs and switches to play with.
### Relative Formatting
As of iOS 4 / OS X 10.6, `NSDateFormatter` supports relative date formatting for certain locales with the `doesRelativeDateFormatting` property. Setting this to `YES` would format the date of `[NSDate date]` to "Today".
## Re-Using Formatter Instances
Perhaps the most critical detail to keep in mind when using formatters is that they are _extremely_ expensive to create. Even just an `alloc init` of an `NSNumberFormatter` in a tight loop is enough to bring an app to its knees.
Therefore, it's strongly recommended that formatters be created once, and re-used as much as possible.
If it's just a single method using a particular formatter, a static instance is a good strategy:
~~~{objective-c}
- (void)fooWithNumber:(NSNumber *)number {
static NSNumberFormatter *_numberFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_numberFormatter = [[NSNumberFormatter alloc] init];
[_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
});
NSString *string = [_numberFormatter stringFromNumber:number];
// ...
}
dispatch_once
guarantees that the specified block is called only the first time it's encountered.
If the formatter is used across several methods in the same class, that static instance can be refactored into a singleton method:
+ (NSNumberFormatter *)numberFormatter {
static NSNumberFormatter *_numberFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_numberFormatter = [[NSNumberFormatter alloc] init];
[_numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
});
return _numberFormatter;
}
If the same formatter is privately implemented across several classes, one could either expose it publicly in one of the classes, or implement the static singleton method in a category on NSNumberFormatter
.
If your app deals in numbers or dates, NSFormatter
is indespensable. Actually, if your app doesn't… then what does it do, exactly?
Presenting useful information to users is as much about content as presentation. Invest in learning all of the secrets of NSNumberFormatter
and NSDateFormatter
to get everything exactly how you want them.
And if you find yourself with formatting logic scattered across your app, consider creating your own NSFormatter
subclass to consolidate all of that business logic in one place.
FormatterKit has great examples of
NSFormatter
subclasses for addresses, arrays, colors, locations, ordinal numbers, time intervals, and units of information.