Search code examples
swiftformattingfoundationmeasurement

Format distance measurement as either kilometers or imperial miles, not metric miles


I'm trying to format a Measurement<UnitLength> in either kilometers or miles, but not Scandinavian miles if the measurement is > 10 km and the locale is appropriate.

I want to use Swift and Foundation's new .formatted API but have not found the proper documentation to adjust the unit styling.

In short I want to present distance like the following examples:

  • If shown in miles, show 3.5 mi
  • If shown in kilometers, show 7.1 km
  • If shown in kilometers but more than 10 km, show 13.5 km, right now it shows (in Swedish locale) 1.4 mil

I'm using the following code in SwiftUI to format the distance text

Text(distance.formatted(.measurement(width: .abbreviated, usage: .road)))

Here's some test code that I'm using to show the issue:

import SwiftUI

struct TestView: View {
    @Environment(\.locale) var locale

    private let distance1 = Measurement(value: 7.04765, unit: UnitLength.kilometers)
    private let distance2 = Measurement(value: 13.4657, unit: UnitLength.kilometers)
    
    var body: some View {
        VStack {
            Text(distance1.formatted(.measurement(width: .abbreviated, usage: .road).locale(locale)))
            Text(distance2.formatted(.measurement(width: .abbreviated, usage: .road).locale(locale)))
        }
    }
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            TestView() // Swedish
                .environment(\.locale, .init(identifier: "sv_SE"))
            TestView() // American English
                .environment(\.locale, .init(identifier: "en_US"))
            TestView() // French
                .environment(\.locale, .init(identifier: "fr_FR"))
        }
        .previewLayout(.sizeThatFits)
        .padding()
    }
}

This code generates the following:


Solution

  • .general instead of .road will give you kilometres or miles according to locale. .asProvided will give you the units defined on the original measurement.

    I think .general gets you closest to what you want, though it may end up giving you metres or yards for non-kilometre ranged values.