NSLengthFormatter get stringFromMeters: in miles/kilometers only
Asked Answered
O

4

5

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?

Overseas answered 20/6, 2015 at 7:41 Comment(5)
have you tried setting a different numberFormatter and playing around with the different properties of NSNumberFormatterAlidia
Yes, I did. I have tried setting the number of fraction digits and rounding modes, there was nothing that had an effect on the measurement output. I am still looking through the properties, but was hoping someone had already achieved the same.Overseas
See this answer.Naked
@Naked Thanks, this is basically what I wanted (and will have to implement). But for the sake of the question, how to use NSLengthFormatter, the new class available with iOS 8, to achieve the same result?Overseas
I've no idea, but what all you want is to format <number><unit> and that can be done easily using NSNumberFormatter.Naked
O
10

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)
        }
    }
Overseas answered 20/6, 2015 at 11:31 Comment(1)
Nice, thanks. I respectfully disagree with Sulthan--in a mapping application, I think you really do want to stick to one unit so that users don't have to do a conversion in their head. That restaurant is 20 miles away, or 0.3 miles away--don't ever show feet or yards, thanks much. :)Chela
B
3

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)

Basile answered 20/6, 2015 at 10:15 Comment(5)
That's what I have done. Check the answer. You're right, this requirement is not very common. My client however, does not like distance showing up in yards. In countries with the imperial system, I'm sure people have no problem seeing distance formatted in meters.Overseas
@Overseas You would be amazed to know that many people in USA don't understand the metric system at all :)Basile
Distances on the map, and also on the road, are always in km (when using the metric system), so it's actually a very common requirement.Hampson
@Hampson Not true. I live in a country with a metric system and distances in metres are everywhere on the road and also in map navigation. Once a distance is small enough, it's common to use a smaller unit.Basile
It's actually a very common requirement for people not using metric system (Yes I know...). In metric system it makes sense to go from Kilometers to Meters, but if you follow the same logic you'll go from Miles to Yards to Foot. Usually you don't want to use Yards for road distances and you don't want to display 5,000 ft but 0.9 mi.Acanthocephalan
A
2

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)
    }
}
Acanthocephalan answered 11/11, 2018 at 22:53 Comment(0)
R
0
-(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];
    }
}
Responsiveness answered 9/2, 2022 at 16:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.