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
Uibutton with Single Press and Long Press Events Swift
Get Cellid, Mcc, Mnc, Lac, Signal Strength, Quality and Network in iOS 8.3
How to Display Uiview Over Keyboard in iOS
How to Customise Uislider Height
Does iOS Calendar Support a Url Scheme
Cannot Invoke Initializer for Type 'Double' with an Argument List of Type '(String)'
iOS Custom Annotation: a View Below the Annotation Pin
Extension for Uicolor with Custom Colors It Is Real
Perform Segue with Identifier Wont Work in Swift 2
How to Get the Scnview Camera Position When Using Allowscameracontrol
<Googlemaps/Googlemaps.H> File Not Found Google Maps Sdk for iOS
How to Get Vcf Data with Contact Images Using Cncontactvcardserialization Datawithcontacts: Method
How to Convert Numbers into Text
Ios: Helpfulness of Didreceivememorywarning:
How to Create a PDF File Programmatically in an iOS Application
How to Mute/Unmute Audio When Playing Video Using Mpmovieplayercontroller