🔍

Formatting Person Height vs. Road Distance in Swift

TL;DR: In an Imperial Locale (like the US), a 180 cm table should display as 5.9 ft, but a person’s height should display as 5 ft 11 in. Avoid writing manual unit conversion logic. Instead, utilize the usage parameter in MeasurementFormatStyle (e.g., .personHeight). The system will automatically output the format that best fits local customs based on semantics and locale settings.

The Problem

When developing internationalized apps, developers often face a tricky “context” issue: the same physical quantity requires different expressions depending on the scenario.

The most typical example is Length. Suppose your database stores a value of 180 with the unit centimeters:

  • Scenario A (Furniture Dimensions): If it’s a table, US users expect to see “5.9 ft” (decimal form).
  • Scenario B (Person’s Height): If it’s a user’s height, US users definitely expect to see “5 ft 11 in” (mixed units), not the awkward “5.9 ft”.

To solve this, many developers resort to writing complex if-else logic or manually manipulating MeasurementFormatter. This results in verbose code that is prone to errors in edge cases (such as deciding between 1 ft 0 in and 1 ft).

The Solution

Swift’s FormatStyle API has built-in “semantic awareness.” You don’t need to worry about specific mathematical conversions; you simply need to tell the system “what this value is used for” via the usage parameter.

Based on the current Locale and the provided usage, the system automatically decides:

  1. Unit Selection: Metric (m/cm) vs. Imperial (ft/in/mi).
  2. Formatting Details: Decimal (5.9 ft) vs. Mixed Units (5 ft 11 in).
  3. Scale Adjustment: For road distances, the system automatically determines whether to show meters, kilometers, or miles based on magnitude.

Implementation

The following code demonstrates how to use the .measurement modifier to handle different semantic scenarios:

Swift
import SwiftUI

// Base data: 180 centimeters
let rawMeasurement = Measurement(value: 180, unit: UnitLength.centimeters)

struct MeasurementExample: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            
            // asProvided 180cm
            LabeledContent("asProvided", value: rawMeasurement,
                           format: .measurement(width: .abbreviated,usage: .asProvided))
                .environment(\.locale, .init(identifier: "en_US"))
            
            // Person's Height 5ft, 11in
            LabeledContent("Person Height", value: rawMeasurement,
                           format: .measurement(width: .abbreviated, usage: .personHeight))
                .environment(\.locale, .init(identifier: "en_US"))
            
            // General 5.9ft
            LabeledContent("General", value: rawMeasurement,
                           format: .measurement(width: .abbreviated, usage: .general))
                .environment(\.locale, .init(identifier: "en_US"))
        }
        .padding(30)
        .font(.title)
    }
}

Important Considerations

  • UI Layout Flexibility: Strings generated by personHeight in Imperial environments (like “5 ft 11 in”) are typically longer than their Metric counterparts (“180 cm”). When designing UI (especially in lists or cards), ensure you reserve enough horizontal space or use ViewThatFits to handle potential text truncation.
  • System Control: When you use the usage parameter (especially .road), you surrender control over “which unit to display” to Apple. For very large centimeter values, the system might format them directly as “kilometers” or “miles.” If your business logic requires a specific unit (e.g., must display “meters”), do not use usage. Instead, use .converted(to:) to transform the unit first, then apply basic formatting.

Further Reading

MeasurementFormatStyle is just one part of Swift’s powerful measurement handling system. If you want to dive deeper into handling custom units, comparing performance between new and old Formatters, or mastering other obscure but useful parameters like usage, I recommend reading this deep dive:

Related Tips

Subscribe to Fatbobman

Weekly Swift & SwiftUI highlights. Join developers.

Subscribe Now