Objective c string formatter for distances
I have a distance as a float and I'm looking for a way to format it nicely for human readers. Ideally, I'd like it to change from m to km as it gets bigger, and to round the number nicely. Converting to miles would be a bonus. I'm sure many people have had a need for one of these and I'm hoping that there's some code floating aroun开发者_开发百科d somewhere.
Here's how I'd like the formats:
- 0-100m: 47m (as a whole number)
- 100-1000m: 325m or 320m (round to the nearest 5 or 10 meters)
- 1000-10000m: 1.2km (round to nearest with one decimal place)
- 10000m +: 21km
If there's no code available, how can I write my own formatter?
Thanks
None of these solutions really met what I was looking for, so I built on them:
#define METERS_TO_FEET 3.2808399
#define METERS_TO_MILES 0.000621371192
#define METERS_CUTOFF 1000
#define FEET_CUTOFF 3281
#define FEET_IN_MILES 5280
- (NSString *)stringWithDistance:(double)distance {
BOOL isMetric = [[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue];
NSString *format;
if (isMetric) {
if (distance < METERS_CUTOFF) {
format = @"%@ metres";
} else {
format = @"%@ km";
distance = distance / 1000;
}
} else { // assume Imperial / U.S.
distance = distance * METERS_TO_FEET;
if (distance < FEET_CUTOFF) {
format = @"%@ feet";
} else {
format = @"%@ miles";
distance = distance / FEET_IN_MILES;
}
}
return [NSString stringWithFormat:format, [self stringWithDouble:distance]];
}
// Return a string of the number to one decimal place and with commas & periods based on the locale.
- (NSString *)stringWithDouble:(double)value {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setLocale:[NSLocale currentLocale]];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:1];
return [numberFormatter stringFromNumber:[NSNumber numberWithDouble:value]];
}
- (void)viewDidLoad {
[super viewDidLoad];
double distance = 5434.45;
NSLog(@"%f meters is %@", distance, [self stringWithDistance:distance]);
distance = 543.45;
NSLog(@"%f meters is %@", distance, [self stringWithDistance:distance]);
distance = 234234.45;
NSLog(@"%f meters is %@", distance, [self stringWithDistance:distance]);
}
iOS 7 and OS X 10.9 introduced MKDistanceFormatter for formatting distances:
Code Example:
double myDistance = 21837.0f;
MKDistanceFormatter *df = [[MKDistanceFormatter alloc]init];
df.unitStyle = MKDistanceFormatterUnitStyleAbbreviated;
NSLog(@"myDistance is %@", [df stringFromDistance: myDistance]);
Update:
It seems that MKDistanceFormatter is rounding the input value somehow. E.g. when I set myDistance to 111.0 I get "100 m".
Yes you need to write your own formatter, like
#include <math.h>
NSString* convertDistanceToString(float distance) {
if (distance < 100)
return [NSString stringWithFormat:@"%g m", roundf(distance)];
else if (distance < 1000)
return [NSString stringWithFormat:@"%g m", roundf(distance/5)*5];
else if (distance < 10000)
return [NSString stringWithFormat:@"%g km", roundf(distance/100)/10];
else
return [NSString stringWithFormat:@"%g km", roundf(distance/1000)];
}
...
NSLog(@"The distance is %@", convertDistanceToString(1024));
NSLengthFormatter
which was introduced with iOS 8 and OS X 10.10 is an option people should be aware of.
NSLengthFormatter *lengthFormatter = [[NSLengthFormatter alloc] init];
lengthFormatter.unitStyle = NSFormattingUnitStyleShort;
NSLog(@"distance = %@", [lengthFormatter stringFromMeters:meters]);
However, NSLengthFormatter
has doesn't use Imperial units in those locales where they use metric except for distances.
Here's how I do it. This uses the locale of the user to properly format the string, which you should probably do too.
// Returns a string representing the distance in the correct units.
// If distance is greater than convert max in feet or meters,
// the distance in miles or km is returned instead
NSString* getDistString(float distance, int convertMax, BOOL includeUnit) {
NSString * unitName;
if (METRIC) {
unitName = @"m";
if (convertMax != -1 && distance > convertMax) {
unitName = @"km";
distance = distance / 1000;
}
} else {
unitName = @"ft";
if (convertMax != -1 && distance > convertMax) {
unitName = @"mi";
distance = distance / 5280;
}
distance = metersToFeet(distance);
}
if (includeUnit) return [NSString stringWithFormat:@"%@ %@", formatDecimal_1(distance), unitName];
return formatDecimal_1(distance);
}
// returns a string if the number with one decimal place of precision
// sets the style (commas or periods) based on the locale
NSString * formatDecimal_1(float num) {
static NSNumberFormatter *numFormatter;
if (!numFormatter) {
numFormatter = [[[NSNumberFormatter alloc] init] retain];
[numFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numFormatter setLocale:[NSLocale currentLocale]];
[numFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numFormatter setMaximumFractionDigits:1];
[numFormatter setMinimumFractionDigits:1];
}
return [numFormatter stringFromNumber:F(num)];
}
found this today asking the same question....going with :
NSString *rvalue; if (value > 1000) { rvalue = [NSString stringWithFormat:@"%.02f km",value]; }else { rvalue = [NSString stringWithFormat:@"%.02f m",value]; }
could wrap this in a method, if need be
For those looking for a swift 2.0 version. Ported from @MattDiPasquale
extension Double {
func toDistanceString() -> String {
let METERS_TO_FEET = 3.2808399
let METERS_CUTOFF = 1000.0
let FEET_CUTOFF = 3281.0
let FEET_IN_MILES = 5280.0
let format:String
if (NSLocale.isMetric()) {
if (self < METERS_CUTOFF) {
format = "\(self.stringWithDecimals(0)) metres"
} else {
format = "\((self / 1000).stringWithDecimals(1)) km";
}
} else { // assume Imperial / U.S.
let feet = self * METERS_TO_FEET;
if (feet < FEET_CUTOFF) {
format = "\(feet.stringWithDecimals(0)) feet";
} else {
format = "\((self / FEET_IN_MILES).stringWithDecimals(1)) miles";
}
}
return format
}
func stringWithDecimals(decimals:Int) -> String {
return String(format: "%.\(decimals)f", self)
}
}
extension NSLocale {
class func isMetric() -> Bool {
let locale = NSLocale.currentLocale()
return locale.objectForKey(NSLocaleUsesMetricSystem) as! Bool
}
}
精彩评论