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 asm
for meters,km
for kilometers, andsmi
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 typeUnit
.
4.1 Converting Between and Working with Length Units
Solution
Follow these steps:
- Represent your values first by constructing instances of
Measurement
with your given value. Use one of the units defined inUnitLength
as the unit for your measurement, such asUnitLength.meters
. - 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. - You can also use the
converted(to:)
function of yourMeasurement
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 theUnitLength
base unit, but converting kilometers to hours is not going to work because hours are represented by theUnitDuration
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
4.2 Working with and Switching Between Angle Units
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
4.3 Representing and Converting Between Durations of Time
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
)
(
"
\(
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 PlaygroundPage.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.
4.4 Using and Working with Frequency Units
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
)
(
"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
4.5 Working with and Using Power Units
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.
4.6 Representing and Comparing Temperature Units
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
4.7 Working with and Converting Volume Units
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
(
"
\(
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
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.