Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSLengthFormatter get stringFromMeters: in miles/kilometers only

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?

like image 433
ZeMoon Avatar asked Jun 20 '15 07:06

ZeMoon


3 Answers

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)
        }
    }
like image 82
ZeMoon Avatar answered Nov 12 '22 18:11

ZeMoon


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)

like image 25
Sulthan Avatar answered Nov 12 '22 18:11

Sulthan


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)
    }
}
like image 41
Ludovic Landry Avatar answered Nov 12 '22 19:11

Ludovic Landry