NSLengthFormatter get stringFromMeters: in miles/kilometers only

3.4k Views Asked by At

I am using the NSLengthFormatter class to format the distance between the user and some destination.

CLLocation *userLocation; //<- Coordinates fetched from CLLocationManager
CLLocation *targetLocation; //<- Some location retrieved from server data

CLLocationDistance distance = [userLocation distanceFromLocation:targetLocation];

NSLengthFormatter *lengthFormatter = [NSLengthFormatter new];
NSString *formattedLength = [lengthFormatter stringFromMeters:distance];

Now, if the length is less than 1000 meters, the formatted distance is always shown in yards or meters (depending on the locale).

Eg. if distance = 450.0, the formatted string will be 492.7 yd or 450 m.

How can I tweak NSLengthFormatter to return the distance strings in miles/kilometers only?

4

There are 4 best solutions below

1
On BEST ANSWER

This is what I have ended up using:

-(NSString *)formattedDistanceForMeters:(CLLocationDistance)distance
 {
    NSLengthFormatter *lengthFormatter = [NSLengthFormatter new];
    [lengthFormatter.numberFormatter setMaximumFractionDigits:1];

    if ([[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue])
    {
        return [lengthFormatter stringFromValue:distance / 1000 unit:NSLengthFormatterUnitKilometer];
    }
    else
    {
        return [lengthFormatter stringFromValue:distance / 1609.34 unit:NSLengthFormatterUnitMile];
    }
}

EDIT:

The same in Swift would look like:

func formattedDistanceForMeters(distance:CLLocationDistance) -> String {
        let lengthFormatter:NSLengthFormatter! = NSLengthFormatter()
        lengthFormatter.numberFormatter.maximumFractionDigits = 1

        if NSLocale.currentLocale().objectForKey(NSLocaleUsesMetricSystem).boolValue()
        {
            return lengthFormatter.stringFromValue(distance / 1000, unit:NSLengthFormatterUnitKilometer)
        }
        else
        {
            return lengthFormatter.stringFromValue(distance / 1609.34, unit:NSLengthFormatterUnitMile)
        }
    }
5
On

There doesn't seem a way to opt out of this behaviour. To be honest, your requirement is not very common from UX perspective.

Note that meter is the base unit, not a kilometer (a thousand of meters). Usually, displaying 10 meters is preferred over displaying 0.01 kilometers. It's just more friendly for the users.

It would be actually very hard to design an API that would enforce a specific unit considering that the base unit depends on current locale.

You can enforce a specific unit using:

- (NSString *)unitStringFromValue:(double)value unit:(NSLengthFormatterUnit)unit;

but you will have to handle the locale and scaling & unit conversion by yourself (see Objective c string formatter for distances)

0
On

It's actually a very common requirement for people not using metric system (Yes I know...).

In metric system it just makes sense to go from Kilometers to Meters, etc. If you follow the same logic with the imperial system you'll go from Miles to Yards to Foot.

Usually you don't want to use Yards for road distances for example and you don't want to display 5,000 ft but 0.9 mi (Actually Google Maps display in feet up to 0.1 miles or 528 feet, and then in miles).

let distanceAsDouble = 415.0

let lengthFormatter = LengthFormatter()

if Locale.current.usesMetricSystem {
    return distanceFormatter.string(fromMeters: distanceAsDouble)
} else {
    let metersInOneMile = Measurement<UnitLength>(value: 1.0, unit: .miles).converted(to: .meters).value
    if distanceAsDouble < metersInOneMile / 10 { // Display feets from 0 to 0.1 mi, then miles
        let distanceInFeets = Measurement<UnitLength>(value: distanceAsDouble, unit: .meters).converted(to: .feet).value
        return distanceFormatter.string(fromValue: distanceInFeets, unit: .foot)
    } else {
        return distanceFormatter.string(fromValue: distanceAsDouble / metersInOneMile, unit: .mile)
    }
}
0
On
-(NSString *)formattedDistance {
     
    CLLocationDistance distance = _distance;
     
    NSLengthFormatter *lengthFormatter = [NSLengthFormatter new];
    [lengthFormatter.numberFormatter setMaximumFractionDigits:1];

    if ([[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue])
    {
        return [lengthFormatter stringFromValue:distance / 1000 unit:NSLengthFormatterUnitKilometer];
    }
    else
    {
        return [lengthFormatter stringFromValue:distance / 1609.34 unit:NSLengthFormatterUnitMile];
    }
}