Getting Started with MeasurementFormatter

Login to Access Code

Apple has released over 140 API’s which can make it overwhelming trying to find examples or tutorials on some of the more obscure API classes and methods. While the core team at Apple continues to refine and update the SDK, they rarely seem to have the time to update their examples and documentation to reflect all the additions and changes. Sure, it is easy enough to find the raw documentation of frameworks through the development portal; however, the amount of classes in a framework like Foundation alone can be in the hundreds! Consider that each of these classes may include hundreds more properties, methods and structs contained within them. Have you grasped the total complexity of Foundation yet? Based on some simple estimation we can assume that there are thousands of functions and properties contained within the Foundation framework alone! No wonder it can be difficult to find real-world examples of implementation for all of these various classes.

At least for one class, you need not fret. We are going to deep dive into one class today: NSMeasurementFormatter or in Swift 3+, simply MeasurementFormatter. The MeasurementFormatter class provides properly formatted, localized representations of units and measurements. You can use this class to create localized representations of measurements when displaying quantities of units to the end user. For example, you may have some raw data representing the average length of the Komodo Dragon stored in a unit of measure at 10 feet. If you wanted to present this information to an audience outside of the US, you would probably want to convert and present this measurement in meters instead. NSMeasurementFormatter is a great utility class with a lot of convenient methods to do such work for you.

Up and Running Quickly

There are a few customizable properties of MeasurementFormatter which allow you to change the way in which the results are presented to the end user, but all of these have defaults if you just want to get up and running with the results quickly:

// Initialize an instance of the MeasurementFormatter class
// The default measurement formatter is not very helpful, converting feet to miles
// The first prints as: "10.0 ft"
// The second line prints as: "0.002 mi"
let komodoMeasurement = Measurement(value: 10, unit: UnitLength.feet)
MeasurementFormatter().string(from: komodoMeasurement)

While we get some immediate results from our Measurement class, MeasurementFormatter defaults to converting the feet into miles, which is far from useful in our case. Let’s dive into some of the various properties we can change on a MeasurementFormatter instance.

Understanding Locales

Locales are typically used to provide, format, and interpret information about and according to the user’s customs and preferences. They are frequently used in conjunction with formatters. Although you can use many locales, you usually use the one associated with the current user. In an application, you would probably prompt the user to set their locale at the first initial load. From that point on, you would likely want to store the locale preference in UserDefaults and allow the user to change this setting at anytime under some type of setting view. Let’s look at some code for this, but keep in mind, we are just hardcoding some things for these examples:

// You would probably collect this from a UIPickerView
// and replace "en_US" with the result of the picker
// maybe falling back to a default like the "en_US"
UserDefaults.standard.set("en_US", forKey: "locale")

// From this point on, we would want to refer to the saved preference
// First we will create an MeasurementFormatter instance that we can configure
// Then we retrieve the string identifier from UserDefaults with a default fallback
// Last we initiate our Locale class with this identifier
let measurementFormatter = MeasurementFormatter()
let preferredLocaleIdentifier = UserDefaults.standard.object(forKey: "locale") ?? "en_US"
let preferredLocale = Locale(identifier: preferredLocaleIdentifier as! String)

// Optionally set the `locale` property of our instance.
// If we do not set this, it will default to the user's device locale
measurementFormatter.locale = preferredLocale

// If we print now, we'll see "0.002 mi"
measurementFormatter.string(from: komodoMeasurement)

Making Measurements Human Readable

We saw that the results thus far have defaulted to really broad units of measure such as miles/kilometers. The same is true for other units of measure: weight, mass, etc. To fix this, we can simply set the unitOptions property of our MeasurementFormatter instance to use the naturalScale property of MeasurementFormatter.UnitOptions. Further, we can also customize the way the unit of measure is displayed by simply specifying .long, .medium and .short:

// Optionally set the `unitOptions` property
// This can be: .providedUnit, .naturalScale, or .temperatureWithoutUnit 
// Options provided by MeasurementFormatter.UnitOptions
// This defaults to a privately defined format if unset
measurementFormatter.unitOptions = .naturalScale

// If we print our string, we'll see "10 ft"
measurementFormatter.string(from: komodoMeasurement)

// Optionally set the `unitStyle` property
// This can be: .short, .medium or .long
// Options provided by Formatter.UnitStyle and defaults to .medium if unset
measurementFormatter.unitStyle = .long

// If we print our string, we'll see "10 feet"
measurementFormatter.string(from: komodoMeasurement)

Our last property to customize is the numberStyle. This property expects an instance of NumberFormatter which could lead us down another rabbit hole of properties and methods. We will stick to a simple example in which we convert numbers to text:

// Set the `numberFormatter` property to a customized instance of NumberFormatter
// This can be: .none, .decimal, .currency, .percent, .scientific, .spellOut
// Options provided by NumberFormatter.Style and defaults to .decimal if unset
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .spellOut
measurementFormatter.numberFormatter = numberFormatter

// If we print our string, we'll see "ten feet"
measurementFormatter.string(from: komodoMeasurement)

Wrapping Up

There really is not much more involved with working with MeasurementFormatter than this. We have covered all of the customizable properties, but obviously there is much more you can do with other enum options and number formats should you need to go deeper. It’s worth noting that in addition to the string(from: measurement) method, there is also one other, which prints only the unit of measure itself: string(from: unit).

I hope that you have a deeper understanding of this API in the Foundation framework. With this knowledge and approach to MeasurementFormatter, you can expect to see similar patterns and definitions across many of the other formatter classes, so you intrinsically comprehend many of the other API’s if you followed this article closely.

Happy coding!