skip to Main Content

I’m looking for a best practice, or standard/accepted method of handling both metric and imperial measurements in an app. I have an iOS app that is currently just in the US market, but am wanting to internationalize it for other markets. Part of this involves handling both metric and imperial measurements, and displaying the correct one based on localization or preference overrides.

As this app is designed for people traveling, it is likely that a user may want to switch between one system and the other depending on what country they are currently in, so I will need to manage both sets of data.

Since the user could enter the measurements in either system, and want to read the equivalent in the other, I need some way of maintaining them both. My first thought is to create all measurements (length, volume, mass, temp, pressure) as tuples and calculate the missing part when the data is saved. But as this is my first time dealing with two measurement systems at once, I wonder if there’s already a standard way of handling such data types.

2

Answers


  1. I’d recommend utilizing the Measurement API provided by Apple in the Foundation framework. This provides a way to encapsulate a unit of measure and a double value, offering a range of methods for comparing and converting between different units.

    Simple example for distance measurement:

    import Foundation
    
    enum MeasurementSystem {
        case metric
        case imperial
    }
    
    class MeasurementManager {
        private var system: MeasurementSystem = .metric
    
        func changeSystem(to system: MeasurementSystem) {
            self.system = system
        }
    
        func getUserPreferredDistance(for distanceInMeters: Double) -> String {
            let measurement = Measurement(value: distanceInMeters, unit: UnitLength.meters)
    
            switch system {
            case .metric:
                let formatter = MeasurementFormatter()
                formatter.unitStyle = .short
                formatter.unitOptions = .providedUnit
                return formatter.string(from: measurement)
            case .imperial:
                let imperialMeasurement = measurement.converted(to: .miles)
                let formatter = MeasurementFormatter()
                formatter.unitStyle = .short
                formatter.unitOptions = .providedUnit
                return formatter.string(from: imperialMeasurement)
            }
        }
    }
    

    In the above code:

    1. We have an enum MeasurementSystem to represent the unit systems we will support: .metric and .imperial.
    2. The MeasurementManager class stores the user’s current preference for a measurement system. The changeSystem(to:) function allows switching the measurement system.
    3. The getUserPreferredDistance(for:) function receives a distance in meters, creates a Measurement object from it, and depending on the user’s preferred system, it converts this distance to the appropriate unit and formats it for display.

    You can repeat similar procedures for other types of measurements (volume, mass, temperature, etc.) and store the users’ preference in persistent storage like UserDefaults for future sessions.

    With this approach, you’re leveraging Swift’s built-in functionality for managing and converting between units, rather than re-inventing the wheel.

    Login or Signup to reply.
  2. I would suggest using the Measurement framework. Create a Measurement using init(value:unit:) and display the results (localized for the current locale settings) in the UI using formatted:

    E.g.

    let measurement1 = Measurement(value: 3, unit: UnitLength.meters)
    let string = measurement1.formatted()
    

    Or:

    let measurement2 = Measurement(value: 9, unit: UnitLength.feet)
    let string = measurement2.formatted()
    

    If you want to allow the user to select imperial/metric within your app and want to show the results in something other than the device’s default locale, you can use MeasurementFormatter for fine-grained control over the localization and display of the results in your UI.

    Anyway, Measurment stores the two properties, value and unit, in their original values, avoiding any unnecessary rounding that can happen if you were to store the value as an arbitrarily fixed unit of measure in a single Double.

    FWIW, you can even use this model object (the Measurement is Codable) in your app. Or, if you want to avoid entangling your backend in the Measurement implementation details, you could always store the value and the string identifier for the Unit as two fields.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search