From 180 cm to 5′ 11″: A Complete Guide to Swift Measurement

Published on

In everyday life we constantly convert values between different units of measurement. For developers this seems easy—write a few formulas, sprinkle in a couple of switch statements and you’re done. But the moment you try to support dozens of units, seamless internationalisation, formatting, precision and rounding, the workload sky-rockets and the drudgery can make you question your life choices. The good news: starting with iOS 10 Apple added a comprehensive Measurement API to Foundation, taking all that “donkey work” off our hands. This article walks you through its usage and best practices.

Getting to Know Measurement

Measurement is a struct that represents a numeric value together with its unit:

Swift
value: Double  // numeric value
unit : Unit    // its unit

Measurement gives you type-safe unit conversion. Once instantiated it is immutable, following the value-type semantics of struct.

Swift
// A plain number
let height: Double = 180

// Number plus unit
let heightMeasurement = Measurement(value: 180, unit: UnitLength.centimeters)

// Generic syntax, explicitly stating the unit type
let heightMeasurement = Measurement<UnitLength>(value: 180, unit: UnitLength.centimeters)

Compared with storing a bare Double, Measurement is clearer semantically and comes with convenient conversion:

Swift
// Convert directly to metres
let metres = heightMeasurement.converted(to: .meters).value
print(metres)  // 1.8

Foundation ships with dozens of unit categories—length, mass, duration, acceleration, area, temperature and many more—covering almost every everyday need.

Mathematical Operations

As long as two Measurement values belong to the same unit category (UnitType), you can perform maths on them—addition, subtraction, comparison, even range definitions:

Swift
let heightMeasurement = Measurement<UnitLength>(value: 180, unit: .centimeters)

// Division with a scalar
let half = heightMeasurement / 2
print(half.value)  // 90.0
print(half)        // "90.0 cm"

// Addition: different units can be added; the result is in the category’s base unit
let h180cm = Measurement<UnitLength>(value: 180, unit: .centimeters)
let h1m    = Measurement<UnitLength>(value: 1,   unit: .meters)
let totalHeight = h180cm + h1m   // 2.8 m (base unit of UnitLength is metres)
print(totalHeight)               // "2.8 m"

// Comparison with automatic unit handling
h180cm < h1m  // false (180 cm ≮ 1 m)

// Ranges respect units too
let range = h1m ... h180cm
range.contains(Measurement(value: 6, unit: .feet)) // false (~1.83 m is outside)

No manual conversion, unit information retained throughout—readable and safe.

Tip: When you add or subtract two Measurements of the same category, the result is automatically expressed in that category’s base unit. We’ll discuss base units shortly.

Formatting

Turning a Measurement into user-friendly, localised text is a common task. The formatted() method makes it painless.

The simplest form:

Swift
let h180cm = Measurement<UnitLength>(value: 180, unit: UnitLength.centimeters)
h180cm.formatted()

Output varies by the current locale:

  • United States (en-US) → 5.9 ft (auto-converted to feet)
  • China (zh-CN) → 1.8 m (auto-converted to metres)

Need fine-grained control? Provide a MeasurementFormatStyle:

Swift
let str = h180cm.formatted(
    .measurement(
        width: .wide,                   // wide / abbreviated / narrow
        usage: .asProvided,             // .asProvided / .person / .road / …
        numberFormatStyle: .number
            .precision(.fractionLength(2)) // numeric formatting
    )
)
print(str)  // "180.00 centimeters"
ParameterDescription
widthLevel of unit detail. wide → “centimeters”, abbreviated → “ cm”, narrow → “cm”.
usageTells the formatter the context so it can pick the most appropriate unit and rounding. For instance .personHeight might render 180 cm as “5 ft 11 in” in imperial regions, whereas .road would aggregate into road-friendly units.
numberFormatStyleFull power of FormatStyle for the numeric part—fraction length, grouping, scientific notation, etc.

The formatter not only converts units based on locale and usage, it translates unit names (e.g. centimeters vs 厘米), giving a truly local experience.

Elegant Display in SwiftUI

Text’s init(_:format:) accepts a FormatStyle, so you can display Measurement values that automatically react to the environment’s locale:

Swift
struct MeasurementDisplay: View {
    let h180cm = Measurement<UnitLength>(value: 180, unit: UnitLength.centimeters)

    var body: some View {
        VStack(alignment: .leading) {
            // Example 1: US English, .person context, abbreviated
            Text(h180cm, format: .measurement(width: .abbreviated, usage: .person))
                .environment(\.locale, .init(identifier: "en_US"))
            // Change to .personHeight to show "5 ft, 11 in"

            // Example 2: Chinese, .person context, abbreviated
            Text(h180cm, format: .measurement(width: .abbreviated, usage: .personHeight))
                .environment(\.locale, .init(identifier: "zh_CN"))

            // Example 3: French, .road context, wide
            Text(h180cm, format: .measurement(width: .wide, usage: .road))
                .environment(\.locale, .init(identifier: "fr_FR"))
            // Output: "2 mètres" (1.8 m rounded to 2 m for road usage)
        }
    }
}

image-20250430102805983

If you want different styling for the value and the unit (color, font, weight), format to an AttributedString first:

Swift
struct AttributedMeasurementView: View {
    let weight = Measurement(value: 100, unit: UnitMass.kilograms) // 100 kg
		
  // Use `.attributed` to format a Measurement as an AttributedString
    var styledWeightAttributedString: AttributedString {
        var str = weight.formatted(
            .measurement(
                width: .abbreviated,
                usage: .asProvided,
                numberFormatStyle: .number)
                .attributed
        )
        // Precisely control the styles applied to the value and the unit
        str.transformingAttributes(\.measurement) { info in
            switch info.value {
            case .value:
                str[info.range].font = .title
            case .unit:
                str[info.range].foregroundColor = .orange
                str[info.range].font = .italic(.body)()
            default:
                break
            }
        }
        return str
    }

    var body: some View {
        Text(styledWeightAttributedString)
            .foregroundStyle(.blue.gradient)
    }
}

image-20250430103548462

For an in-depth look at AttributedString in SwiftUI, see:

Old Wine in a New Bottle: Bridging with NSMeasurement

Used NSMeasurement before and wonder if Measurement is a brand-new Swift implementation? Under the hood they share the same logic:

Swift
public struct Measurement<UnitType>: ReferenceConvertible, Comparable, Equatable
  where UnitType: Unit { }

Thanks to ReferenceConvertible, Measurement (value type) and NSMeasurement (reference type) convert seamlessly:

Swift
let h1m  = Measurement(value: 1, unit: UnitLength.meters)
let ns   = h1m as NSMeasurement           // Swift → Obj-C
print(ns.doubleValue)                     // 1

let nsCM = NSMeasurement(doubleValue: 100, unit: UnitLength.centimeters)
let cm   = nsCM as Measurement            // Obj-C → Swift
print(cm.unit)                            // cm

Swift Foundation is being re-architected in pure Swift, but Measurement hasn’t been rewritten yet. It currently works only on Apple platforms. Other platforms provide a stub implementation so your code still compiles, but these stubs do not perform real conversion or formatting. We look forward to true cross-platform support.

Adding Units to an Existing Category

Sometimes the unit you need belongs to an existing category (mass, length…) but isn’t built-in. Swift’s extension lets you add it easily.

Example: add the Chinese “jin” (市斤, = 0.5 kg) to UnitMass:

Swift
extension UnitMass {
    /// Chinese mass unit: jin
    /// 1 jin = 0.5 kilograms
    static var jin: UnitMass {
        // 1. Symbol
        let symbol = NSLocalizedString("", comment: "Symbol for the Chinese unit of mass 'jin'")

        // 2. Converter
        // UnitMass’s base unit is kilograms
        let converter = UnitConverterLinear(coefficient: 0.5)

        // 3. Create and return
        return UnitMass(symbol: symbol, converter: converter)
    }
}

let weight = Measurement(value: 1, unit: UnitMass.kilograms)
print(weight.converted(to: .jin).value) // 2.0
  • symbol – text representation of the unit. NSLocalizedString is wise for future localisation (e.g. “Jin” in English).
    • Limitation: Custom units don’t automatically provide .wide, .abbreviated, .narrow variants like system units; you usually just get the symbol you supplied.
  • converter – defines the relationship to the base unit of the category.
    • Most quantities use UnitConverterLinear (y = a x).
    • For units requiring an offset (y = a x + b, e.g. temperature) use UnitConverterTemperature.

Defining an Entirely New Unit Category

If neither system categories nor extending them suffices—for example you need a brand-new physical quantity or a non-standard system—you can create a custom category.

Understand Foundation’s hierarchy:

  • Unit – root of all units.
  • Dimension – abstract subclass of Unit for measurable units related by linear conversion.
  • UnitLength, UnitMass, UnitDuration, … – concrete subclasses of Dimension with their own units and base units.

So: subclass Dimension.

Example: traditional Chinese mass units (jin, liang, qian):

Swift
/// Chinese mass units: jin, liang, qian
final class UnitChineseMass: Dimension, @unchecked Sendable {
    // Units: jin (base), liang, qian
    static let jin   = UnitChineseMass(symbol: "", converter: UnitConverterLinear(coefficient: 1))     // base unit
    static let liang = UnitChineseMass(symbol: "", converter: UnitConverterLinear(coefficient: 0.1))   // 1 liang = 0.1 jin
    static let qian  = UnitChineseMass(symbol: "", converter: UnitConverterLinear(coefficient: 0.01))  // 1 qian  = 0.01 jin

    // Declare the base unit
    override class func baseUnit() -> UnitChineseMass { .jin }
}

let weight = Measurement(value: 1, unit: UnitChineseMass.jin)
weight.converted(to: .liang).value // 10
weight.converted(to: .qian).value  // 100

Your custom category enjoys all Measurement features (though localisation of symbols is up to you).

Make the Most of Foundation—Twice the Result for Half the Effort

As we’ve seen, the Measurement API embodies years of Foundation wisdom: thoughtful design, thorough coverage and built-in internationalisation. Before rolling your own “simple” solution, make a habit of checking Foundation’s docs—you’ll often find an official, battle-tested alternative with better performance. Using these ready-made wheels boosts productivity, hardens your codebase and lets you focus on core logic. After all: Less code, more life!

Weekly Swift & SwiftUI highlights!