Categories
Foundation iOS macOS Swift

Measurement, Unit, Dimension, and MeasurementFormatter on iOS

I was looking at formatters provided by Foundation framework and this time I would like to put some attention on MeasurementFormatter. Like the name says, it is used for creating localized strings of some sort of measurements. Measurements are represented by a generic struct Measurement where the generic UnitType describes the unit represented by it. Apple provides an Unit subclass Dimension which in turn has a many subclasses on its own. At the time of writing there are 22 dimensional units available with each of those having multitude of related units. For example, UnitDuration provides units for seconds, minutes, and hours. The full list of Apple provided dimensions are available in a table here: Dimension.

Using MeasurementFormatter

MeasurementFormatter is simple to use. If we change the unitStyle property then we can configure how the unit is spelled in the localized string.

let formatter = MeasurementFormatter()
let unitStyles: [Formatter.UnitStyle] = [.short, .medium, .long]
for unitStyle in unitStyles {
formatter.unitStyle = unitStyle
let measurement = Measurement(value: 9.8, unit: UnitAcceleration.gravity)
print(formatter.string(from: measurement))
}
// Prints:
// 9.8Gs
// 9.8 G
// 9.8 g-force
Basic usage of MeasurementFormatter.

MeasurementFormatter also has an unitOptions property which controls the way how the final string is composed when taking account the current locale. For example, if locale is set to en_US then UnitTemperature measurement is formatted in Fahrenheits. If locale is set to en_GB then the measurement returns Celsius.

let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_US")
print(formatter.string(from: Measurement(value: 293, unit: UnitTemperature.kelvin)))
// 67.73°F
formatter.locale = Locale(identifier: "en_GB")
print(formatter.string(from: Measurement(value: 293, unit: UnitTemperature.kelvin)))
// 19.85°C
MeasurementFormatter selecting output unit based on the locale.

In case we would like to make sure the same unit is used, then we can use the providedUnit option.

let formatter = MeasurementFormatter()
formatter.unitOptions = [.providedUnit]
formatter.locale = Locale(identifier: "en_US")
print(formatter.string(from: Measurement(value: 293, unit: UnitTemperature.kelvin)))
// 293 K
Forcing the formatter to use the provided unit.

Another thing to note is that the Measurement type also supports comparing measurements and mathematical operations. For example, we can add two measurements.

let measurement1 = Measurement(value: 3.2, unit: UnitElectricCurrent.amperes)
let measurement2 = Measurement(value: 0.02, unit: UnitElectricCurrent.kiloamperes)
print(measurement1 + measurement2)
// 23.2 A
A sum of two measurements.

Creating additional units

MeasurementFormatter is built in a way that it can support custom units as well. We could create a pseudo unit for a children’s game named UnitBanana.

class UnitBanana: Dimension {
override static func baseUnit() -> Self {
return UnitBanana.banana as! Self
}
static let banana = UnitBanana(symbol: "bana", converter: UnitConverterLinear(coefficient: 1.0))
}
let formatter = MeasurementFormatter()
formatter.unitOptions = .providedUnit
print(formatter.string(from: Measurement(value: 2, unit: UnitBanana.banana)))
// 2 bana
Pseudo-unit called UnitBanana.

Summary

MeasurementFormatter and Measurement types create a powerful combination which can be used for creating localized strings of values with an unit. Next time when you need to present a value with an unit, then check out the MeasurementFormatter first.

If this was helpful, please let me know on Mastodon@toomasvahter or Twitter @toomasvahter. Feel free to subscribe to RSS feed. Thank you for reading.

Leave a comment