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?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
ZeMoon
  • 20,054
  • 5
  • 57
  • 98
  • have you tried setting a different `numberFormatter` and playing around with the different properties of [NSNumberFormatter](https://developer.apple.com/library/prerelease/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNumberFormatter_Class/index.html#//apple_ref/doc/c_ref/NSNumberFormatter) – luk2302 Jun 20 '15 at 07:49
  • 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. – ZeMoon Jun 20 '15 at 08:04
  • See [this answer](http://stackoverflow.com/questions/2324125/objective-c-string-formatter-for-distances). – Droppy Jun 20 '15 at 08:55
  • @Droppy 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? – ZeMoon Jun 20 '15 at 08:58
  • I've no idea, but what all you want is to format and that can be done easily using `NSNumberFormatter`. – Droppy Jun 20 '15 at 09:02

4 Answers4

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)
        }
    }
ZeMoon
  • 20,054
  • 5
  • 57
  • 98
  • 6
    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. :) – SimplGy Apr 13 '16 at 20:48
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)

Community
  • 1
  • 1
Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 2
    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. – ZeMoon Jun 20 '15 at 11:34
  • 2
    @ZeMoon You would be amazed to know that many people in USA don't understand the metric system at all :) – Sulthan Jun 20 '15 at 11:36
  • 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. – lenooh Dec 11 '16 at 20:38
  • @lenooh 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. – Sulthan Dec 11 '16 at 20:59
  • 3
    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. – Ludovic Landry Nov 11 '18 at 22:39
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)
    }
}
Ludovic Landry
  • 11,606
  • 10
  • 48
  • 80
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];
    }
}
Ofir Malachi
  • 1,145
  • 14
  • 20