Chapter 4. Measurements and Units

We’ve all been there! You need to convert one unit to another, and you begin your journey, most of the time, by Googling what the conversion should be. You can now use some built-in structures to represent and convert your units.

The following classes and structures appear throughout this chapter:

Unit
The base class for all the units that are in the SDK itself. This class defines a symbol for the unit, such as m for meters.
Dimension
The class that inherits from Unit and defines the converter to be used between various units.
UnitLength, UnitMass, and the like
Basic units that inherit from Dimension. Each unit offers alternative ways of representing a particular measure, such as length or mass. Each unit also standardizes the various symbols for its measure, such as m for meters, km for kilometers, and smi for Scandinavian miles (with each Scandinavian mile being equal to 10 kilometers).
Measurement
The base structure for defining a value with a unit. Every measurement has a value of type Double and a unit of type Unit

4.1 Converting Between and Working with Length Units

Problem

You want to be able to represent values with the unit of length, such as kilometers and miles, and would like to be able to perform some basic tasks on them, such as converting one unit to another, or adding and subtracting values represented in different units.

Solution

Follow these steps:

  1. Represent your values first by constructing instances of Measurement with your given value. Use one of the units defined in UnitLength as the unit for your measurement, such as UnitLength.meters.
  2. After you have your Measurement instances, you can use the various operators such as + and - between them as long as they are from the same base unit.
  3. You can also use the converted(to:) function of your Measurement structure instances to convert your values to another unit type of the same base unit. For instance, converting meters to miles is fine, as they are both from the UnitLength base unit, but converting kilometers to hours is not going to work because hours are represented by the UnitDuration unit.

Discussion

Your values are representable by instances of the Measurement structure with a given unit. Let’s create two values, one for 5 meters and the other for 1 kilometer:

let meters = Measurement(value: 5, unit: UnitLength.meters) // 5.0 m
let kilometers = Measurement(value: 1, unit: UnitLength.kilometers) // 1.0 km

You can then check out the return value of type(of:) on these values to see what data type they have:

type(of: meters) // Measurement<UnitLength>
type(of: kilometers) // Measurement<UnitLength>

Their data type is Measurement, which itself is generic, and its generic parameter is set to UnitLength since both values are lengths.

You can then simply add these values together if you want:

let result = meters + kilometers // 1005.0 m
type(of: result) // Measurement<UnitLength>

This + operator is defined in the Foundation framework as follows:

public func +<UnitType : Dimension>(lhs: Measurement<UnitType>,
              rhs: Measurement<UnitType>) -> Measurement<UnitType>

Eventually, you can convert the result into various other units of length, such as miles:

let finalKilometers = result.converted(to: .kilometers) // 1.005 km
let finalMeters = result.converted(to: .meters) // 1005.0 m
let finalMiles = result.converted(to: .miles) // 0.6224 mi
let finalScandinavianMiles = result.converted(to: .scandinavianMiles) 
                                                   // 0.1005 smi

If you wish to present these values to the user, which are of type Measurement<Unit>, read the value and the unit.symbol properties from them. The value will be of type Double and the unit.symbol of type String. This gives you the information you need to display values on UI components, such as a UILabel instance.

See Also

Recipe 4.2

4.2 Working with and Switching Between Angle Units

Problem

You want to use, convert, represent, and display angles in your applications without having to convert them manually.

Solution

Just like length units (see Recipe 4.1), values that represent an angle can also be encapsulated inside an instance of the Measurement structure. The unit is UnitAngle.

Discussion

Let’s have a look at how you can represent 100 gradians in your application:

let gradians = Measurement(value: 100, unit: UnitAngle.gradians) // 100.0 grad

You can then convert this value to degrees using the convert(to:) function of the Measurement structure:

gradians.converted(to: UnitAngle.degrees) // 90 degrees

And if you read the return value of type(of:) on this value, you will get the value of Measurement<UnitAngle>:

type(of: gradians) // Measurement<UnitAngle>

Similarly, you can represent degrees with the Measurement structure:

let degrees = Measurement(value: 180, unit: UnitAngle.degrees) // 180.0

And just like the + operator we saw used before with Measurement types, you also have a - operator that is defined like so:

public func -<UnitType : Dimension>(lhs: Measurement<UnitType>,
rhs: Measurement<UnitType>) -> Measurement<UnitType>

You can use this operator between any two instances of the Measurement structure as long as their base units are the same:

let total = gradians - degrees // -90 degrees

Once you have your angle measurements, you can convert them to each other:

let finalGradians = total.converted(to: .gradians) // -100 grad
let finalDegrees = total.converted(to: UnitAngle.degrees) // -90 degrees

Additionally, you can show this value to your users with the value: Double and unit.symbol: String properties of your Measurement instance:

let string = "\(finalDegrees.value) \(finalDegrees.unit.symbol)" 
                                     // "-90 degrees"

See Also

Recipe 4.1

4.3 Representing and Converting Between Durations of Time

Problem

You want to represent units of time with their values and the type of unit they represent, such as hours or seconds, but you don’t want to fuss with counting in bunches of 60 to calculate conversions between units.

Solution

To solve this problem, instantiate the Measurement structure with your time values and use the UnitDuration for your base unit. You can then use +, -, and other basic operators between your units without worrying about what unit they are represented with, as long as they come from the UnitDuration base unit.

Discussion

Let’s have a look at an example of how we can convert hours, minutes, and seconds to one another, but let’s spice it up a little bit. It’s clear that we can use Measurement to represent all three values with UnitDuration, but we can instead extend Double so that any number can then be turned into an hour, minute, or second value represented by Measurement:

extension Double{
  var hours: Measurement<UnitDuration>{
    return Measurement(value: self, unit: UnitDuration.hours)
  }
  var minutes: Measurement<UnitDuration>{
    return Measurement(value: self, unit: UnitDuration.minutes)
  }
  var seconds: Measurement<UnitDuration>{
    return Measurement(value: self, unit: UnitDuration.seconds)
  }
}

Now that this is done, we can put together a few values using these properties:

let trainJourneyDuration = (1.25).hours
trainJourneyDuration.converted(to: .minutes) // 75.0 min

let planeJourneyDuration = (320.0).minutes
planeJourneyDuration.converted(to: .hours) // 5.333 hr

let boatJourneyDuration = (1500.0).seconds
boatJourneyDuration.converted(to: .minutes) // 25.0 min

These values each represent a sub-journey of a bigger journey from one destination to another and they are in minutes, hours, and seconds. We can put them all together inside an array and calculate their total value in minutes, using each Measurement instance’s convert(to:) method:

let journeys = [
  trainJourneyDuration,
  planeJourneyDuration,
]

let finalJourneyDurationInMinutes = journeys.reduce(0.0){
  return $0 + $1.converted(to: UnitDuration.minutes).value
}

finalJourneyDurationInMinutes // 395

Representing time with Measurement makes it much easier to work with existing classes such as Timer. For instance, if you want a timer that runs for n seconds, all you have to do is create a Measurement instance of type UnitDuration.seconds and then, once the measurement’s value property is less than or equal to 0, you can invalidate the timer:

import UIKit
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

extension Double{
  var seconds: Measurement<UnitDuration>{
    return Measurement(value: self, unit: UnitDuration.seconds)
  }
}

var remainingTime = Measurement(value: 10, unit: UnitDuration.seconds)
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
  let minutesRemaining = remainingTime.converted(to: UnitDuration.minutes)
  print("\(minutesRemaining.value) minutes remaining before the timer stops")
  remainingTime = remainingTime - (1.0).seconds
  if remainingTime.value <= 0.0{
    timer.invalidate()
  }
}
Note

The PlaygroundSupport framework is used alongside the Playground​Page.current.needsIndefiniteExecution: Bool property, which you can set to true if you need an infinite loop in your playground  so that your playground doesn’t just start at one point and end at another. In contrast with the default behavior of playgrounds, starting at the top and ending after the execution of the last line of code in the playground, yours becomes a fully fledged application that lives until you ask it to stop.

See Also

Recipes 4.1 and 4.4

4.4 Using and Working with Frequency Units

Problem

You want to use and convert between frequency units, such as megahertz and gigahertz.

Solution

Represent your values with the Measurement structure and use UnitFrequency as the base unit. The UnitFrequency class has various class variables such as:

  • terahertz
  • gigahertz
  • megahertz
  • kilohertz

Discussion

If you build computers in your spare time (as I used to do more frequently, before I had three children!), you’ll see keywords such as megahertz and gigahertz all over the place. It’s a great idea to represent all these values with some structure in Swift, and with Measurement now you can do that by choosing UnitFrequency as your base unit. 

Here is an example of representing two CPU clock speeds in Swift, using gigahertz and then megahertz:

var myCpuClock = Measurement(value: 3.5, unit: UnitFrequency.gigahertz)
var yourCpuClock = Measurement(value: 3400, unit: UnitFrequency.megahertz)

You can then use the built-in > and < operators to see which values are bigger or smaller:

if myCpuClock > yourCpuClock{
  "My CPU is faster than yours."
} else if yourCpuClock > myCpuClock{
  "Your CPU is faster than mine. Good for you!"
} else {
  "It seems our CPU clocks are the same!"
}

These two operators are defined for you already in the Foundation framework so that you don’t have to write them yourself:

public func ><UnitType : Dimension>(lhs: Measurement<UnitType>,
   rhs: Measurement<UnitType>) -> Bool

public func <<UnitType : Dimension>(lhs: Measurement<UnitType>,
   rhs: Measurement<UnitType>) -> Bool

Now that you have two CPUs whose clock speeds are represented in various forms of the frequency unit, you can put them inside an array and iterate through this array to get their clock speeds shown in gigahertz:

let baseUnit = UnitFrequency.gigahertz
[myCpuClock, yourCpuClock].enumerated().forEach{offset, cpuClock in
  let converted = cpuClock.converted(to: baseUnit)
  print("CPU #\(offset + 1) is \(converted.value) \(converted.unit.symbol)")
}

And the output will be as shown here:

CPU #1 is 3.5 GHz
CPU #2 is 3.4 GHz

See Also

Recipe 4.1

4.5 Working with and Using Power Units

Problem

You want to be able to convert between and use power units, but you don’t want to lift a finger and do any of the work manually yourself.

Solution

Simply use Measurement to represent your power units with the unit equal to UnitPower and then use the convert(to:) function of the Measurement structure to convert your values to other power units, some of which are listed here:

  • terawatts
  • gigawatts
  • megawatts
  • kilowatts
  • watts
  • horsepower

Discussion

Let’s check out an example. Let’s say that you are riding a bicycle and moving forward by putting 160 watts of energy into the pedals. Now, a super-duper cyclist that has won three Tour de France tournaments has a pedaling power of 0.40 horsepower. Are you putting more power into the pedals than this super cyclist, or the other way around? How can you find the answer without having to convert one of these values to the other or both values to another base unit?

Well, the answer is quite easy. Simply represent these values with Measurement:

let myCyclingPower = Measurement(value: 160, unit: UnitPower.watts)
let superCyclistPower = Measurement(value: 0.40, unit: UnitPower.horsepower)

And then use the > and < operators that are already defined for you to find out which value is larger:

if myCyclingPower > superCyclistPower{
  "Wow, I am really strong."
} else if myCyclingPower < superCyclistPower{
  "The super cyclist is of course stronger than I am."
} else {
  "It seems I am as strong as the super cyclist!"
}

But how does iOS do this, and how does it know how to compare these values? The answer is simple: base units. If you Command-click UnitPower in Xcode, you will see some code like this:

@available(iOS 10.0, *)
public class UnitPower : Dimension, NSSecureCoding {

   /*
    Base unit - watts
    */

There you can see that the base unit is watts. iOS converts all your power units to watts and then compares their value properties to find which one is higher.

See Also

Recipes 4.6 and 4.7

4.6 Representing and Comparing Temperature Units

Problem

You want to convert between and work with temperature units, such as Celsius and Fahrenheit, without having to do any manual work.

Solution

To avoid having to convert different temperature units, encapsulate your temperature values inside an instance of the Measurement structure with the UnitTemperature unit type. Then you can use the convert(to:) method of the Measurement structure to convert different types to each other and also use the existing greater-than, less-than, and other operators to manipulate or compare these measurements.

Discussion

Let’s have a look at an example. Say that we have three temperatures of types Celsius, Fahrenheit, and Kelvin and our goal is to convert them all to Celsius and then sort them in ascending order. Let’s first represent our temperatures:

let cakeTemperature = Measurement(value: 180, unit: UnitTemperature.celsius)
let potatoesTemperature = Measurement(value: 200, unit: 
                            UnitTemperature.fahrenheit)
let beefTemperature = Measurement(value: 459, unit: UnitTemperature.kelvin)

Next we can sort them by their Celsius values in an ascending order:

let sorted = [cakeTemperature, potatoesTemperature, beefTemperature]
  .sorted { (first, second) -> Bool in
    return first.converted(to: .celsius) < second.converted(to: .celsius)
}

When we have a sorted array, we can convert all the values to Celsius to get our final sorted array of Celsius temperatures:

let allCelsiusTemperatures = sorted.map{
  $0.converted(to: .celsius)
}

allCelsiusTemperatures // 93.33, 180, 185.8

See Also

Recipe 4.5

4.7 Working with and Converting Volume Units

Problem

You need to work with values represented as volumes such as liters and pints, but you don’t want to manually do the work of comparing and converting them.

Solution

Encapsulate your values inside instances of the Measurement structure with the unit type UnitVolume.

Discussion

Imagine that you are baking a cake and three of the ingredients that you need are represented in different units, namely liters, deciliters, and pints:

let milk = Measurement(value: 2, unit: UnitVolume.liters)
let cream = Measurement(value: 3, unit: UnitVolume.deciliters)
let water = Measurement(value: 1, unit: UnitVolume.pints)

You can add all these values together with the + operator and convert the total to various other volumes, such as cups:

let total = milk + cream + water
let totalDeciliters = total.converted(to: .teaspoons)
let totalLiters = total.converted(to: .tablespoons)
let totalPints = total.converted(to: .cups)

You can also go through all the values and print their details, such as their raw values and the symbols that represent their units:

func showInfo(for measurement: Measurement<UnitVolume>){
  let value = measurement.value
  let symbol = measurement.unit.symbol
  print("\(value) \(symbol)")
}

[totalDeciliters, totalLiters, totalPints].forEach{showInfo(for: $0)}

The output printed to the console will be similar to this:

562.633599246894 tsp
187.544025752698 tbsp
11.5549 cup

See Also

Recipe 4.2

Get iOS 11 Swift Programming Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.