Choosing Units with Measurementformatter

Choosing units with MeasurementFormatter

You just need to convert your UnitLength from meters to feet. You can also create a custom US measurement formatter to display it as needed:

extension Measurement where UnitType == UnitLength {
private static let usFormatted: MeasurementFormatter = {
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.unitOptions = .providedUnit
formatter.numberFormatter.maximumFractionDigits = 0
formatter.unitStyle = .long
return formatter
}()
var usFormatted: String { Measurement.usFormatted.string(from: self) }
}

Playground

let value: Double = 1839
let meters: Measurement<UnitLength> = .init(value: value, unit: .meters)
let feet = meters.converted(to: .feet)
let formatted = feet.usFormatted
print(formatted) // "6,033 feet"\n

Using MeasurementFormatter with Derived unit

You need to update a few things in your code.

Firstly, you are using the string method on Formatter which takes Any? and returns an optional String. If you change the parameter name to from, you will use the method defined on MeasurementFormatter which returns a non-optional:

var measureStringCustom = formatter.string(from: measureCustom)

Secondly, you are using a MeasurementFormatter which has the unitOptions property set to .naturalScale (the default). If you change this to, .providedUnit, you'll see that you now get some output. The problem is that .naturalScale will use the appropriate unit for the given locale and there is currently no way to set what that is for custom Dimension subclasses.

So, the way to achieve what you what is to use the converted method along with a .providedUnit formatter, like so:

let converted = measureCustom.converted(to: .metricTonsPerHour)
var formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
print(formatter.string(from: converted))

Finally, you are probably still not getting the output you expect. This is because the coefficient for the UnitConverterLinear which is returned by baseUnit should be 1. I expect you intended to define your dimension as follows (notice the scaled-down coefficients):

open class UnitFlowRate : Dimension {
open override static func baseUnit() -> UnitFlowRate { return self.metricTonsPerHour }
static let shortTonsPerHour = UnitFlowRate(symbol: NSLocalizedString("stph", comment: "short tons per hour"), converter: UnitConverterLinear(coefficient: 0.5))
static let metricTonsPerHour = UnitFlowRate(symbol: NSLocalizedString("mtph", comment: "metric tons per hour"), converter: UnitConverterLinear(coefficient: 1))
}

Use MeasurementFormatter to display meters as feet

You should set the formatter's unitOptions to naturalScale:

let measurementFormatter = MeasurementFormatter()
measurementFormatter.unitOptions = .naturalScale

And you should only set the locale if you want every user in any locale to see the value in the specific locale.

Swift MeasurementFormatter incorrectly converting distance to miles

Unfortunately the world is not that simple. Some locales use the metric system, but for historical reasons use miles for distances and speed. The base English locale (en) is one such, while the Australian English locale (en_au) uses metric for all measurements including speed and distance.

To get the behaviour you want you do not need to be concerned with the locale. You are specifying the Measurement with a unit of .kilometers, so you simply need to set the .providedUnit unitOptions on your MeasurementFormatter to instruct it to ignore the locale when formatting the measurement:

func distanceString(for distance: Double) -> String {

let distanceFormatter = MeasurementFormatter()
distanceFormatter.unitOptions = .providedUnit
let measurement = Measurement(value: distance, unit: UnitLength.kilometers)

return distanceFormatter.string(from: measurement)
}

Get value of localized Unit from Measurement

What I think you are after is the measurement system for the locale. The Mac currently understands three systems: metric, US and UK. The UK uses metric for lengths and weights but still uses miles/mph for road distances and speeds, hence the third option here – the usesMetricSystem property only supports two options and returns true for the UK.

To get the measurement system in Swift requires using NSLocale, this extension will add it to Locale:

extension Locale
{
var measurementSystem : String?
{
return (self as NSLocale).object(forKey: NSLocale.Key.measurementSystem) as? String
}
}

For added "fun" Apple doesn't actually specify the values of this property, they give examples but not a full definition. You can get the known three values querying the property on three locales known to use them, e.g. you could add the following to the extension:

   static let metricMeasurementSystem = Locale(identifier: "fr_FR").measurementSystem!
static let usMeasurementSystem = Locale(identifier: "en_US").measurementSystem!
static let ukMeasurementSystem = Locale(identifier: "en_UK").measurementSystem!

Of course if Swift did support them it would probably define an enum for the possibilities, you could do that as well.

HTH

BTW: For those who think the UK is being awkward, the "US" system is only used in three countries: US, Myanmar and Liberia.

How can I handle the MeasurementFormatter using the kilometersPerHour UnitType?

Here is some example

    NSMeasurementFormatter *formatter = [[NSMeasurementFormatter alloc] init];
formatter.unitStyle = NSFormattingUnitStyleMedium;
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US"];

// format 100 km/h

formatter.unitOptions = NSMeasurementFormatterUnitOptionsProvidedUnit;
NSUnitSpeed *speed = [NSUnitSpeed kilometersPerHour];
NSString *speedString = [formatter stringFromMeasurement:[[NSMeasurement alloc] initWithDoubleValue:100.0 unit:speed]];

NSLog(@"kilometers %@", speedString);

// format 100 mph

formatter.unitOptions = NSMeasurementFormatterUnitOptionsProvidedUnit;
speed = [NSUnitSpeed milesPerHour];
speedString = [formatter stringFromMeasurement:[[NSMeasurement alloc] initWithDoubleValue:100.0 unit:speed]];

NSLog(@"miles %@", speedString);

// convert 100 km/h to 62.137 mph for en_US locale

formatter.unitOptions = NSMeasurementFormatterUnitOptionsNaturalScale;
speed = [NSUnitSpeed kilometersPerHour];
speedString = [formatter stringFromMeasurement:[[NSMeasurement alloc] initWithDoubleValue:100.0 unit:speed]];

NSLog(@"miles to kilometers %@", speedString);

How to get a unit of measurement as a singular noun?

It looks like the only native way to do this would be create an explicit measurement and manually remove the singular unit ("1"):

let measurementFormatter = MeasurementFormatter()
measurementFormatter.unitStyle = .long
measurementFormatter.unitOptions = .providedUnit

let length: Measurement<UnitLength> = .init(value: 1, unit: .kilometers)
let str = measurementFormatter.string(from: length)
.replacingOccurrences(of: "1", with: "")
.replacingOccurrences(of: " ", with: " ")
.trimmingCharacters(in: .whitespaces)

I'm no language expert but I expect this will work with most (if not all) languages.



Related Topics



Leave a reply



Submit