SwiftUI For Masterminds 3rd Edition
SwiftUI For Masterminds 3rd Edition
SwiftUI For Masterminds 3rd Edition
for Masterminds
How to take advantage of Swift and SwiftUI
to create insanely great apps for
iPhones, iPads, and Macs
J.D Gauchat
www.jdgauchat.com
SwiftUI for Masterminds
Copyright © 2022 John D Gauchat
All Rights Reserved
Apple™, iPhone™, iPad™, Mac™, among others mentioned in this work, are
trademarks of Apple Inc.
CHAPTER 7 - LISTS
7.1 LISTS OF VIEWS
FOREACH VIEW
SCROLLVIEW VIEW
LAZY GRIDS
7.2 LIST VIEW
SECTIONS
EDIT MODE
SWIPE ACTIONS
CUSTOM BUTTONS
REFRESHABLE
OUTLINE LIST
7.3 TABLES
7.4 PICKERS
PICKER VIEW
DATE PICKERS
7.5 FORMS
FORM VIEW
DISCLOSURE GROUP
CHAPTER 8 - NAVIGATION
8.1 MULTIPLE VIEWS
NAVIGATION STACK
TOOLBAR
SEARCH
NAVIGATION LINK
8.2 MODAL VIEWS
SHEETS
POPOVERS
ALERT VIEWS
CONFIRMATION DIALOG
8.3 TAB VIEWS
8.4 UNIVERSAL INTERFACE
THREE-COLUMNS LAYOUT
CONFIGURATION
CHAPTER 9 - CONCURRENCY
9.1 ASYNCHRONOUS AND CONCURRENT TASKS
TASKS
ASYNC AND AWAIT
ERRORS
CONCURRENCY
ACTORS
MAIN ACTOR
ASYNCHRONOUS SEQUENCES
TASK GROUP
ASYNCHRONOUS IMAGES
CHAPTER 10 - STORAGE
10.1 USER PREFERENCES
APP STORAGE
10.2 FILES
URLS AND PATHS
FILES AND DIRECTORIES
FILE ATTRIBUTES
FILE CONTENT
BUNDLE
DOCUMENTS
10.3 ARCHIVING
ENCODING AND DECODING
JSON
10.4 CORE DATA
DATA MODEL
CORE DATA STACK
MANAGED OBJECTS
FETCH REQUEST
ASYNCHRONOUS ACCESS
CORE DATA APPLICATION
PREVIEWS
SORT DESCRIPTORS
PREDICATES
MODIFYING OBJECTS
DELETING OBJECTS
CUSTOM FETCH REQUESTS
SECTIONS
TO-MANY RELATIONSHIPS
CHAPTER 11 - GRAPHICS AND ANIMATIONS
11.1 SHAPES
COMMON SHAPES
GRADIENTS
EFFECTS
PATTERNS
11.2 PATHS
PATH VIEW
CUSTOM SHAPES
11.3 TRANSFORMATIONS
11.4 CANVAS
11.5 CHARTS
11.6 IMAGE RENDERER
11.7 ANIMATIONS
ANIMATING CUSTOM SHAPES
CANVAS ANIMATIONS
TRANSITIONS
CHAPTER 12 - GESTURES
12.1 GESTURE RECOGNIZERS
GESTURE MODIFIERS
HIT TESTING
12.2 GESTURE STRUCTURES
TAP GESTURE
LONG PRESS GESTURE
MAGNIFICATION GESTURE
ROTATION GESTURE
DRAG AND DROP GESTURE
CHAPTER 13 - MAPKIT
13.1 MAP VIEW
ANNOTATIONS
LOCAL SEARCH
USER LOCATION
CHAPTER 14 - NOTIFICATIONS
14.1 NOTIFICATION CENTER
SYSTEM NOTIFICATIONS
14.2 USER NOTIFICATIONS
USER NOTIFICATIONS FRAMEWORK
MEDIA ATTACHMENTS
PROVISIONAL NOTIFICATIONS
NOTIFICATIONS DELEGATE
GROUPS
SUMMARY
ACTIONS
14.3 APP STATES
APP DELEGATES
CHAPTER 15 - ICLOUD
15.1 DATA IN THE CLOUD
ENABLING ICLOUD
TESTING DEVICES
15.2 KEY-VALUE STORAGE
15.3 ICLOUD DOCUMENTS
METADATA QUERY
SINGLE DOCUMENT
MULTIPLE DOCUMENTS
15.4 CLOUDKIT
ENABLING CLOUDKIT
IMPLEMENTING CLOUDKIT
CUSTOM IMPLEMENTATION
RECORDS
ZONES
QUERY
ASYNCHRONOUS OPERATIONS
BATCH OPERATIONS
REFERENCES
CLOUDKIT DASHBOARD
CUSTOM CLOUDKIT APPLICATION
ASSETS
SUBSCRIPTIONS
ERRORS
DEPLOY TO PRODUCTION
CHAPTER 17 - WEB
17.1 WEB
LINKS
SAFARI VIEW CONTROLLER
WEBKIT FRAMEWORK
WEB CONTENT
CHAPTER 18 - MEDIA
18.1 PICTURES
PHOTOS PICKER
CAMERA
STORING PICTURES
SHARE LINK
CUSTOM CAMERA
18.2 VIDEO
VIDEO PLAYER
CUSTOM VIDEO PLAYER
18.3 COLOR PICKER

The Basic label represents topics you can ignore if you already know
the basics of Swift and app development. If you are learning how to
develop applications for Apple devices for the first time, these
sections are required.

The Medium label represents topics that are not required for every
app. You can ignore the information presented in these sections until
they are applied in practical situations later or when you need them
in your own applications.

The Advanced label represents topics that are only required in
advanced applications or API development. The information
presented in these sections is not required for the development of
most applications, but it can be helpful if you want to improve your
understanding of how Apple technologies work.
If you are new to app development, read all the Basic sections first,
and only read the Medium sections later when you need to understand
how the examples work.
Examples
The welcome screen offers a list of recent projects on the right and buttons
on the left to create a new project, open a project on our computer, or
clone one stored in a repository.
1.3 Development

Even though some simple projects could be developed without
programming a single line of code, we always need to write our own code
if we want to create a useful application, and for that, we need
programming languages, frameworks, and APIs.
Programming Languages

This opens a window with a list of icons to select the template we want to
use. Templates are files with pre-programmed code to help us get started
with our project. The ones available at this time are called Blank (with just
a few lines of code to start from scratch), Game (with basic code to
program a video game), Map (with the code to display a map), and Single
View (with the same code required to create the user interface for an
application).
After the template is selected, Xcode asks for the name of the Playground
file and where we want to store it. Next, Xcode shows the Playground's
interface on the screen. Figure 2-3, below, shows what we see when we
create a Blank template.
Playground presents a simple interface with a toolbar at the top and four
areas: the Navigator Area where we can see the resources included in our
Playground project, the Editor Area where we write our code, the Results
Side Bar on the right where the results produced by our code are
displayed, and the Console at the bottom where we can read the errors
produced by the code and print our own messages.
The interface includes buttons to open and remove some of these panels.
The button in the upper left corner removes the Navigator Area (number
1), the one in the upper right corner controls a panel called Utilities Area
with information about the selected resource, and the button in the lower
right corner opens or removes the Console.
As illustrated in Figure 2-3, the Editor Area includes a button at the bottom
of the panel to run and stop the code (Play Button). There is also a play
button on the left side of the Editor Area that we can press if we want to
execute parts of the code instead (number 4). When this button is pressed,
the code is executed up to the line in which the button is located.
Playground can run the code automatically or wait until we press the Play
button. By default, the mode is set to Automatically Run, but we can press
and hold the Play button to access a menu that allows us to modify this
behavior.
In the Editor Area, we can see the code we have programmed so far. When
a new Playground file is created, Xcode offers a template that includes a
few basic lines of code to start with. Listing 2-1, below, is the code
currently generated for the Blank template.
The button on the right is called Quick Look, and it shows a popup window
with a visual representation of the result produced by the execution of the
code, such as formatted text or an image. In this case, no visual effect is
generated by the code, so we only see plain text.

The button on the left is called Show Result, and what it does is to open a
window within our code with a visual representation of the results of the
execution of the code over time. In this case, nothing changes, so only the
"Hello, playground" text is shown.
The code provided by Xcode for the Blank template is useless, but it shows
the basic syntax of the Swift language and how to do elemental things in a
program such as importing frameworks to add functionality and storing
data in memory. The reason why one of the statements is storing data in
memory is because this is the most important task of a program. A
program’s main functions are storing, retrieving, and processing data.
Working with data in the computer’s memory is a delicate process that
demands meticulous organization. If we are not careful, data may be
accidentally deleted, corrupted, or completely overwritten. To make sure
this does not happen, programming languages introduce the concept of
variables.
2.2 Variables

Variables are names representing values stored in memory. Once a variable
is defined, its name remains the same but the value in memory it
represents may change. This allows us to store and retrieve a value from
memory without having to remember where in the memory the value was
stored. With just mentioning the name of the variable we used to store the
value, we can get it back or replace it with a new one.
When we use variables, the system takes care of managing the memory for
us, but we still need to understand how memory works in order to know
what kind of values we can store.
Memory

With these two cells, we can now represent up to four states (4 possible
combinations). If we had used three cells instead, then the possible
combinations would have been 8 (eight states). The number of
combinations doubles every time we add another cell to the group. This
can be extended to represent any number of states we want. Because of
this characteristic, this system of switches is used to represent binary
numbers, which are numbers expressed by only two digits: 0 and 1. An on
switch represents the value 1 and an off switch represents the value 0.
Basic units were determined with the purpose of identifying parts of this
endless series of digits. One cell was called a bit and a group of 8 bits was
called a Byte. Figure 2-10, below, shows how a Byte looks like in memory,
with some of its switches on representing the binary number 00011101.
Numbers of one numeral system, like binary, can be converted to any other
numeral system, like decimal. The binary system is the one a computer can
understand because it translates directly to the electronic switches they
are built with, but humans find this difficult to read, so we use other
systems to express numbers, like the decimal system. For instance, the
possible combinations of 8 bits are 256, therefore, a Byte can represent
decimal numbers from 0 to 255. (If the Byte in our example is converted to
the decimal system, we get the number 29).
To represent larger numbers, Bytes are grouped together. For example, if
we take two Bytes from memory, we get a binary number composed of a
total of 16 bits (16 zeros and ones). A binary number of 16 bits can
represent decimal numbers from 0 to 65535 (a total of 65536 possible
combinations). To establish clearly defined data structures, every
programming language declares its own units of data of a predetermined
size. These units are usually called primitive data types.
Primitive Data Types

Int—This data type defines integer numbers, which are numbers with
no fractional component. In 64 bits systems, the size of this data type
is 8 Bytes and therefore it can store values from
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
If we calculate the size of each type presented so far and determine the
possible combinations of bits, we will discover that the maximum values
don't match. For example, an Int8 uses 1 Byte, which means it is composed
of 8 bits, and for this reason it should be able to store numbers from 0 to
255 (256 possible combinations). The reason why an Int8 has a positive
limit of 127 is because it only uses 7 bits to store the value, the first bit on
the left is reserved to indicate the sign (positive or negative). Although
these limits are not restrictive, the language also provides the unsigned
versions of these types in case we need to store larger positive values.
UInt—This is the same as Int but for unsigned values. Because it does
not reserve a bit for the sign, in 64-bit systems it can store values from
0 to 18,446,744,073,709,551,615.
The specific data types for UInt are UInt8, UInt16, UInt32, and UInt64. These
data types work exactly like the equivalents for Int, but they are intended
to store only positive numbers.
Although all these data types are very useful, they are only good for storing
binary values that can be used to represent integer numbers. Arithmetic
operations also require the use of real numbers (e.g., 3.14 or 10.543).
Computers cannot reproduce these types of values, but they can work with
an approximation called floating-point numbers. The following are the
most frequently used floating-point data types defined in the Swift
language.
Floating-point types can handle large numbers using scientific notation, but
because of their precision, it is recommended to declare a variable of type
Doublewhen performing calculations and use Float for minor tasks, such as
storing coordinates to position graphics on the screen.
Declaration and Initialization

This example creates a variable called mynumber of type Int. When the
system reads this statement, it reserves a space in memory 8 Bytes long
(64 bits) and assigns the name mynumber to that space. After the execution
of this statement, we can use the variable mynumber to store in memory any
integer value from -9,223,372,036,854,775,808 to
9,223,372,036,854,775,807.
IMPORTANT: You can use any character you want to declare the
name of a variable, except for spaces, mathematical symbols, and
some Unicode characters. Also, the name cannot start with a
number, and Swift distinguishes between lowercase and uppercase
characters (MyInt is considered a different variable than myint). You
must also make sure that the name does not match any reserved
word. If you declare a variable with an illegal name, Xcode will show
you an error.
The memory is a reusable resource. The space reserved for a variable may
have been used before by another variable or a piece of code may have
been stored in the same location. For this reason, after the declaration of a
variable we must always store a value in it to clear the space. This action is
called Initialization.
In the new example of Listing 2-3, we first declare the variable as we did
before, and then initialize it with the value 5 (we store the number 5 in the
space of memory reserved for this variable). To store the value, we use the
= (equal) symbol and the syntax name = value, where name is the name of the
variable and value is the value we want to store (once the variable was
declared, we do not have to use the var instruction or specify its type
anymore).
Most of the time, we know what the variable’s initial value will be right
away. In cases like this, Swift allows us to declare and initialize the variable
in just one line of code.
Of course, we can create all the variables we want and of the data type we
need.
The first statement in Listing 2-6 declares an integer variable and initializes
it with the value 5. The second statement does the same but for a floating-
point variable. When the data type of a value is easy to identify, Swift can
infer it and the syntax may be simplified, as shown next.
Swift infers the variable’s data type from the value we are trying to assign
to it. In this last example, the value 5 is clearly an integer and the value
14.129 is clearly a floating-point value, so Swift creates the variable
mynumber of type Int and the variable myfavorite of type Double (it selects the
most comprehensive type).
IMPORTANT: Xcode offers a simple tool you can use to see the data
type assigned to a variable and get additional information. All you
need to do is click on the name of the variable while holding down
the Option key. This opens a popup window with the full declaration
of the variable, including its data type, and any information we may
need to identify the code’s functionality. As we will see later, this not
only applies to variables but also to instructions, including properties
and methods.
Storing values in memory is what variables allow us to do, but those values
do not have to be declared explicitly, they can also be the result of
arithmetic operations. Swift supports the operations: + (addition), -
(subtraction), * (multiplication), / (division) and % (remainder).
When the system reads the statement in Listing 2-9, it adds 10 to 5 and
assigns the result to mynumber (15).
Of course, we can perform not only addition but any operation we want.
This example declares and initializes three variables. In the first statement
both numbers were declared as floating-point values, so the compiler
infers a Double and creates the myfraction1 variable of that type. In the second
statement, we have an integer value and a floating-point value. Because of
the floating-point value, the compiler interprets the integer (5) as a Double
(5.0) and creates the myfraction2 variable of type Double. But in the last
statement there is no clear floating-point value. Both numbers were
declared as integers (with no fractional part). In this case, the compiler
does not know what we want to do, so it interprets both numbers as
integers and creates the myfraction3 variable of type Int. When an operation
produces a result that is expected to be an integer, any fractional part is
discarded. In this example, the system gets rid of the decimal 5 from the
result and only assigns the integer 2 to the variable. If we don't want to
lose the fractional part, we must avoid inference and declare the data type
explicitly as Float or Double.
Dividing integer numbers is pointless most of the time, except in some
circumstances when we need to know the remainder. The remainder is the
amount left over by a division between two numbers and it is calculated
using the % symbol.
Each statement in Listing 2-12 calculates the remainder of dividing the first
number by the second number and assigns the result to the variable. For
instance, the first statement produces the remainder 2. The system divides
11 by 3 and finds a quotient of 3. Then, to get the remainder, it calculates
11 minus the multiplication of 3 times the quotient (11 – (3 * 3) = 2).
The second statement produces a remainder of 4 and the third statement
produces a remainder of 1. This last statement is particularly useful
because it allows us to determine whether a value is odd or even. When
we calculate the reminder of an integer divided by 2, we get a result
according to its parity. If the number is even, the remainder is 0, and if the
number is odd, the remainder is 1 (or -1 for negative values).
Performing arithmetic operations becomes useful when instead of
numbers we use variables.
This example declares the mynumber variable and initializes it with the value
5. In the next statement, the total variable is declared and initialized with
the result of the addition of the current value of mynumber plus 10 (5 + 10).
In Listing 2-13, we used a new variable to store the result of the operation,
but when the old value is not important anymore, we can store the result
back into the same variable.
In this example, the current value of mynumber is added to 10 and the result
is assigned to the same variable. After the execution of the second
statement, the value of mynumber is 15.
Working with values previously stored in a variable allows our program to
evolve and adapt to new circumstances. For instance, we could add 1 to
the current value of a variable and store the result in the same variable to
create a counter. Every time the statement is executed, the value of the
variable is incremented by one unit. Recurrent increments and decrements
of the value of a variable are very important in computer programming.
Because of this, Swift supports two operators that were specifically
designed for this purpose.
IMPORTANT: Swift also offers Overflow operators (&+, &-, &*, &/ and
&%). These operators are useful when we think that an operation
could produce a result that goes over the limit the data type can
handle. For more information, visit our website and follow the links
for this chapter.
Constants

All the rules for variables also apply to constants; with the exception that
we cannot assign a new value after the constant was already initialized.
The mynumber constant declared in Listing 2-16 will always have the value 5.
A character is declared using the Character data type and initialized with the
value between double quotes. In Listing 2-17, we declare a variable called
myletter with the value A.
In addition to the characters on the keyboard, Unicode allows us to store
emojis and symbols. Xcode offers a handy tool to select the graphics we
want. By pressing the combination of keys Control + Command + Space, we
can open a popup window and select a graphic with a click of the mouse.

Strings

In Listing 2-19, the mytext variable is created with the value "My name is "
and then the string "John" is added at the end of the current value to get
the string "My name is John". The += operator works in a similar way, and
we can also combine them to get the string we want.
In this code, we read the age variable and add its current value to the string.
The string "I am 44 years old" is then assigned to mytext. Using this tool, we
can insert any value we want inside a string, including Character and String
values, numbers, and arithmetic operations. In the following example, the
value of age is multiplied by 12 and the result is included in the string.
Boolean variables are a type of variables that can only store two values:
true or false. These variables are particularly useful when we want to execute
an instruction or a set of instructions only if a condition is met. To declare a
Boolean variable, we can specify the data type as Bool or let Swift infer it
from the value, as in the following example.
This example declares an optional integer, assigns the value 5 to it, and
then declares the variable as empty with the keyword nil. Although
optionals seem to work like regular variables, they do not expose their
values. To read the value of an optional, we must unwrap it by adding an
exclamation mark at the end of the name.
The last statement in Listing 2-29 unwraps mynumber to get its value,
multiplies this value by 10, and assigns the result to the total variable. This is
only necessary when we need to use the value. If we just want to assign an
optional to another optional, the process is as always.
In this example, the system infers the type of the total variable to be an
optional of type Int and assigns the value of mynumber to it. If we want to
read the value of total later, we must unwrap it as we did with mynumber
before.
IMPORTANT: Before unwrapping an optional, we need to make sure
it contains a value (it is not equal to nil). If we try to unwrap an
empty optional, the app will return an error and crash. Later in this
chapter we will learn how to use conditional statements to check
this condition.
There are times when we know that an optional will always have a value,
but we do not know what the initial value is. For example, we could have a
variable that receives a value from the system as soon as the application is
executed. When the variable is declared in our code, we do not have a
value to assign to it, but we know that the variable will have a value as
soon as the user launches the app. For these situations, Swift includes
Implicitly Unwrapped Optionals. These are optional variables declared with
the exclamation mark instead of the question mark. The system treats
these variables as optionals until we use them in a statement, as in the
following example.
The second statement in Listing 2-35 assigns a new string to the first value
of the tuple. The data type of the new value must be of the same as the
old one or we will get an error. After the code is executed, the value of
mytext is "George is 44 years old".
Indexes are a quick way to access the values of a tuple, but they do not
help us remember what the values represent. To identify the values in a
tuple, we can assign a name to each one of them. The name must be
declared before the value and separated with a colon, as shown next.
Swift also provides a way to copy the values of the tuple into independent
variables.
The names of the variables are declared between parentheses. The values
are assigned to the variables in the same order they are in the tuple. If only
some of the values are required, the rest may be ignored with an
underscore, as shown next.
Only the variables name and age are created in this last example. (Notice the
underscore in the place of the second variable.) The string assigned to
mytext is "John is 44 years old".
2.4 Conditionals and Loops

Up to this point, we have written the instructions in sequence, one after
the other. In this programming pattern, the system executes each
statement once. It starts with the one at the top and goes on until it
reaches the end of the list. The purpose of Conditionals and Loops is to
break this sequential flow. Conditionals allow us to execute one or more
instructions only when a condition is met, and Loops execute a group of
instructions repeatedly.
If and Else

if age < 21 {
message = "John is young"
}

Two variables are declared in this code. The age variable contains the value
we want to check, and the message variable is the one we are going to
modify depending on the state of the condition. The if statement compares
the value of age with the number 21 using the character < (less than). This
comparison returns the state of the condition (true or false). If the
condition is true (the value of age is less than 21), the instruction between
braces is executed, assigning a new value to the message variable, otherwise,
the instruction is ignored and the execution continues with the instruction
after the braces. In this case, the value of age is less than 21 and therefore
the string "John is young" is assigned to the message variable.
The < symbol we used in the last example to compare values is part of a
group of operators called Comparison Operators. The following is the list of
comparison operators available in Swift.
All these operators are applied as we did with the < operator in the
previous example. For instance, the following code modifies the value of
message when the value of age is less or equal than 21.
This code checks whether the value of the underage variable is true or false. If
it is true (which means the condition is true), a new string is assigned to the
message variable.
If what we want is to execute the statements when the value is false, Swift
offers a logical operator to toggle the condition. All we need to do is to
precede the condition with an exclamation mark.
The original value of the underage variable in the code of Listing 2-42 is true,
so when the if statement toggles the condition, the resulting condition is
false and therefore the value of the message variable is not modified.
The exclamation mark is part of a group of logical operators provided by
Swift.
Logical operators work with any kind of conditions, not only Booleans. To
work with complex conditions, it is recommended to enclose the condition
between parentheses.
The if statement in Listing 2-43 compares the value of the age variable with
21 and checks the value of the smart variable. If age is less than 21 and smart
is true, then the overall condition is true, and a new string is assigned to
message. If any of the individual conditions is false, then the overall condition
is false, and the block of instructions is not executed. In this case, both
conditions are true and therefore the "John is allowed" string is assigned to
message.
IMPORTANT: Using && (AND) and || (OR) you can create a logical
sequence of multiple conditions. The system evaluates one condition
at a time from left to right and compares the results. If you want to
make sure that the expressions are evaluated in the correct order,
you can declare them within parentheses, as in (true && false) ||
true. The expression within the parentheses is evaluated first, and
the result is then evaluated against the rest of the expression.
if myoptional != nil {
let uvalue = myoptional!
count = count + uvalue // 5
}

This example introduces the process we must follow to read the value of
an optional variable. The optional is first compared against nil. If it is
different from nil (which means it contains a value), we unwrap the
optional inside the block of statements using an exclamation mark, assign
its value to a constant, and use the constant to perform any operation
necessary.
We must always make sure that an optional has a value before unwrapping
it. For this reason, Swift introduces a convenient syntax that checks the
optional and unwraps its value at the same time. It is called Optional
Binding, and we can use it in an if statement, as shown next.
This code is cleaner and easy to read. The optional is unwrapped as part of
the condition. If it is different from nil, its value is assigned to the uvalue
constant and the statements in the block are executed, otherwise, the
statements inside the block are ignored.
As we will see later, variables and constants declared inside a block are
only available to the code in the block. This also means that variables and
constants declared in different blocks are independent (their values are
stored in different locations in memory). An interesting consequence of
this is that we can declare the constant for the Optional Binding with the
same name as the variable, and they will be considered by Swift to be
different.
This is a simple example that checks whether a value is odd or even using
the remainder operator. The condition gets the remainder of the division
between the value of the mynumber variable and 2 and compares the result
against 0. If true, it means that the value of mynumber is even, so the first
block is executed. If the result is different from 0, it means that the value is
odd and the condition is false, so the block corresponding to the else
instruction is executed instead.
The statements if and else may be concatenated to check as many
conditions as we need. In the following example, the first condition checks
whether age is less than 21. If not true, the second condition checks
whether age is over 21. And if not true, the final else block is executed.
The first value is returned if the condition is true, and the second value is
returned if the condition is false. The advantage of using a ternary operator
is that it reduces the size of the code significantly, but the result is the
same as using an if else statement. In our example, the string “Underage” is
assigned to the variable message because the value of age is less than 21.
Ternary operators can also be implemented to unwrap optionals. For
instance, we can check whether an optional variable contains a value and
assign it to another variable or give the variable a default value if the
optional is empty.
This code defines an optional variable called age with the value 19. Next, we
unwrap it with a ternary operator and assign its value to a new variable
called realage. If the optional contains a value, its value is assigned to the
variable, otherwise, the value 0 is assigned instead.
Assigning values by default when the optional is empty is very common. To
simplify our work, Swift offers the nil-coalescing operator, which is
represented by the characters ??. This operator works like the ternary
operator implemented in Listing 2-52; it unwraps the optional and returns
its value or returns another value if the optional is empty. In the following
example, we create an empty optional called age and use the nil-coalescing
operator to assign its value to the maxage variable or the value 100 if the
optional is empty.
The switch statement can also work with more complex data types,
such as strings and tuples. In the case of tuples, switch provides additional
options to build complex matching patterns. For example, the following
code checks the second value of a tuple to determine the difference in age.
switch ages {
case (10, 20):
message = "Too close"
case (10, 30):
message = "The right age" // "The right age"
case (10, 40):
message = "Too far"
default:
message = "Way too far"
}

This example always compares the first value of the tuple against 10
but checks different matches for the second value. If a value does not
matter, we can use an underscore to ignore it.
switch ages {
case (_, 20):
message = "Too close"
case (_, 30):
message = "The right age" // "The right age"
case (_, 40):
message = "Too far"
default:
message = "Way too far"
}

switch ages {
case (let x, 20):
message = "Too close to \(x)" // "Too close to 10"
case (_, 30):
message = "The right age"
case (let x, 40):
message = "Too far to \(x)"
default:
message = "Way too far"
}

In this example, when the switch statement checks the first and third
cases, it creates a constant called x and assigns the first value to it, so we
can access and use the value from the statements inside the case. (In this
example, we just add the value to a string.)
There is an even more complex matching pattern that involves the use
of a clause called where. This clause allows us to check additional conditions.
In the following example, we capture the values of the tuple with another
tuple and compare them against each other.
switch ages {
case let (x, y) where x > y:
message = "Too young"
case let (x, y) where x == y:
message = "The same age"
case let (x, y) where x < y:
message = "Too old" // "Too old"
default:
message = "Not found"
}

Every time the switch statement tries to match a case in this example, it
creates a tuple and assigns the values of ages to it. The where clause
compares the values and when the condition is true, it executes the
statements inside the case.
While and Repeat While

If the first time the condition is checked it returns false, the statements in
the block are never executed. If we want to execute the statements at least
once, we must use repeat while.
The purpose of the for in loop is to iterate over collections of values, like the
strings of characters studied before. During the execution of a for in loop,
the system reads the elements of the collection one by one in sequential
order and assigns their values to a constant that can be used by the
statements inside the block. In this case, the condition that must be
satisfied for the loop to be over is reaching the end of the collection.
The syntax of a for in loop is for constant in collection {}, where constant is the
name of the constant that we are going to use to capture the value of each
element in the collection, and collection is the name of the collection of
values that we want to iterate over.
The code in Listing 2-62 defines two String variables: mytext with the text
"Hello" and message with an empty string. Next, we use a for in loop to iterate
over the characters of the string in mytext and add each character to the
current value of message. In each cycle of the loop, the for in instruction takes
one character from the value of mytext, assigns it to the letter constant, and
executes the statements in the block. The first statement uses a ternary
operator to check whether the value of message is an empty string. If not, it
adds the - character at the end of it, otherwise, it adds an empty string.
Finally, the second statement adds the current value of letter to the end of
message.
The code works as follows: in the first cycle, the character "H" is assigned
to the letter constant. Because at this moment the message string is empty,
nothing is added by the first statement in the block. Then, the second
statement adds the value of letter to the current value of message and the
next cycle is executed. In this new cycle, the character "e" is assigned to
the letter constant. This time, the message string already contains the letter
"H", so the character "-" is added at the end by the first statement ("H-"),
and then the second statement adds the letter "e" at the end of this new
string ("H-e"). This process continues until all the characters in the mytext
variable are processed. The final value of message is "H-e-l-l-o".
When the constant is not required inside the block, we can replace it with
an underscore.
for _ in mytext {
counter += 1
}
var message = "The string contains \(counter) letters" // 5

In this example, we iterate over the value of mytext to count the number of
characters in the string. The value of the counter variable is incremented by
1 in each cycle, giving a total of 5.
A for in instruction may include the where clause to perform the next cycle
only when a condition is met. For instance, the following code checks the
value of letter and only performs the cycle when the letter is not an L. In
consequence, only the letters H, e, and o are counted.
Listing 2-64: Adding a condition to a loop

var mytext = "Hello"
var counter = 0
The continue instruction is applied when we do not want to execute the rest
of the statements in the block, but we want to keep the loop running. For
instance, the following code counts the letters in a string but ignores the
letters "l".
The if statement inside the for in loop of Listing 2-65 compares the value of
letter with the letter "l". If the characters match, the continue instruction is
executed, the last statement inside the loop is ignored, and the loop moves
on to the next character in mytext. In consequence, the code counts all the
characters that are different from "l" (H, e, and o).
Unlike the continue instruction, the break instruction interrupts the loop
completely, moving the execution of the program to the statements after
the loop. The following example only counts the characters in the string
that are placed before the first letter "l".
Again, the if statement of Listing 2-66 compares the value of letter with the
character "l", but this time it executes the break instruction when a match is
found. If the character currently processed by the loop is "l", the break
instruction is executed, and the loop is over, no matter how many
characters are left in the string. In consequence, only the characters
located before the first letter "l" are considered (H and e).
The break instruction is also useful to cancel the execution of a switch
statement. The problem with the switch statement in Swift is that the cases
must be exhaustive, which means that every possible value must be
contemplated. When this is not possible or necessary, we can use the break
instruction to ignore the values that are not applicable. For example, we
can declare the cases for the values we need and then break the execution
in the default case for the rest of the values that we do not care about.
Listing 2-67: Ignoring values in a switch statement

var age = 19
var message = ""
switch age {
case 13:
message = "Happy Bar Mitzvah!"
case 16:
message = "Sweet Sixteen!"
case 21:
message = "Welcome to Adulthood!"
default:
break
}

After the execution of this code, the message variable is empty because
there is no case that matches the value of the age variable and therefore the
code in default is executed and the break instruction returns the control to
the statements after the switch.
Guard

The guard instruction is intended to prevent the execution of the code that
follows the statement. For example, we can break the execution of a loop
when a condition is satisfied, as we do with an if else statement.
The guard instruction works along with the else instruction and therefore it is
very similar to the if else statement, but the code is only executed when the
condition is false. In the example of Listing 2-68, the for in loop reads the
characters of the string in mytext one by one, as done before. If the
characters are different from the letter "l", we increment the value of
counter by 1, but when the value of letter is equal to "l", the condition of the
guard instruction is false and therefore the break instruction is executed,
interrupting the loop.
The code in Listing 3-1 declares the mynumber variable, initializes it with the
value 5, and then declares a function called myfunction(). The statements in a
function are only processed when the function is called, so after the
myfunction() function is declared, we call it with the myfunction() instruction.
When our function is called, it multiplies the current value of mynumber
times 2 and assigns the result back to the variable. Once all the statements
inside the function are processed, the execution continues from the
statement after the call.
As we already mentioned, once the function is declared, we can call it any
time necessary. For example, the following code runs a while loop that calls
myfunction() a total of 5 times (the loop runs while counter is less than 5).
Every time the function is executed, mynumber’s current value is multiplied
by 2, getting a result of 160.
func myfunction() {
mynumber = mynumber * 2 // 160
}
while counter < 5 {
myfunction()
counter += 1
}

Functions may not only be called every time we need them, but also the
values we provide to the function in the call may be different each time.
This makes functions reusable.
The constants and variables declared inside a function, like total and message,
are not accessible from other parts of the code. This means that a function
can receive values, but the result produced by processing those values is
trapped inside the function. To communicate the result of an operation to
the rest of the code, functions can return a value using an instruction
called return. The return instruction finishes the processing of the function,
so we must declare it after all the required statements have been
processed, as in the following example.
When we create a function that returns a value, the data type of the value
returned is specified in the declaration after the parentheses with the
syntax -> type, where type is just the data type of the value that is going to be
returned by the function. A function can only return values of the type
specified in its definition. For instance, the function in Listing 3-6 can only
return integer values because we declared the returned type as -> Int.
When a function returns a value, the system calls the function first and
then the value returned is processed inside the statement that made the
call. For instance, in the code of Listing 3-6, we create the result variable and
assign to it a call to the doubleValue() function. When the system processes
this statement, the function is executed first and then the value returned
(50) is assigned to the variable.
The values received and returned by a function may be of any available
data type. The following example takes a string and returns a tuple with a
string and an integer.
Listing 3-7: Returning a tuple

func sumCharacters(word: String) -> (String, Int) {
var characters = ""
var counter = 0
for letter in word {
characters += "\(letter) "
counter += 1
}
return (characters, counter)
}
var (list, total) = sumCharacters(word: "Hello")
var message = "There are \(total) characters (\(list))"

The sumCharacters() function in Listing 3-7 receives a string (word: String) and
returns a tuple composed of a string and an integer (-> (String, Int)). The
function adds the characters to the characters variable and counts them with
the counter variable, as we did before (see Listing 2-63). At the end, the
tuple is returned, its values are assigned to the list and total variables, and
then incorporated into a string ("There are 5 characters (H e l l o )").
Besides returning the result of an operation, the return instruction can also
be used to interrupt the execution of a function. The guard instruction
introduced in Chapter 2 is perfect for cases like this, as illustrated by the
following example.
This code defines two functions: first() and second(). The second() function
receives an inout parameter called value, which means that any modification
on its value is stored in the original variable. The first() function defines a
variable called number and then executes the second() function with it, so
when the second() function multiplies this value times 2, the result (50) is
stored in number. At the end, we execute the first() function to start the
process. Notice that in the call to the second() function we include an
ampersand before the variable's name (&). This tells the system that the
variable is going to be modified by the function.
An important aspect of the definition of a function are the names of the
parameters. For example, the function doubleValue() of previous examples
includes a parameter called number. Every time we call this function, we
must include the parameter's name (e.g., doubleValue(number: 50)). These
names are called Argument Labels. Swift automatically generates argument
labels for every parameter using their names. Sometimes the names
assigned to the parameters of a function may be descriptive enough for
the statements of the function but may be confusing when we perform the
call. For cases like these, Swift allows us to define our own argument labels
in the function's definition; we just need to declare them before the name
of the parameter separated by a space.
Listing 3-11: Declaring argument labels

func doubleValue(years number: Int) -> Int {
number * 2
}
let result = doubleValue(years: 8)
let message = "The result is \(result)" // "The results is 16"

The code in Listing 3-13 declares the function sayhello() with one
parameter of type String called name and with the string "Undefined" as the
default value. When the function is called without a value, the string
"Undefined" is assigned to name.
Generic Functions

Although creating two or more functions with the same name is not
allowed, we can do it if their parameters are not the same. This is called
Overloading and allows us to define multiple functions with the same
name to process different types of values.
The functions in Listing 3-14 have the same name, but one receives an
integer and the other a string. We can say that the function that receives
the string overloads the function that receives the integer. When we call
the getDescription() function, the system selects which function is going to be
executed depending on the value of the argument. (When we call the
function with an integer, the first function is executed, and when we call it
with a string, the second function is executed.)
The advantage of creating functions with the same name is that there is
only one name to remember. We call the function and Swift takes care of
executing the right one depending on the values assigned to the
arguments. But when the functions perform the same task and only differ
in the type of value received, we end up with two or more pieces of code
to maintain, which can introduce errors. In cases like this, we can declare
only one function with a generic data type.
Generic data types are placeholders for real data types. When the function
is called, the generic data type is turned into the data type of the value
received. If we send an integer, the generic data type turns into an Int, if we
send a string, it becomes a String. To define a generic function, we must
declare the generic data type using a custom name between angle brackets
after the function's name, as in the following example.
This function is a generic function. The generic data type was called T
(this is a standard name for a generic data type, but we can use any name
we want). The function performs the same task, and it has the same name
than the two functions from the previous example, but now we have
reduced the amount of code in our program. When the function is called,
the T generic data type is converted into the data type received and the
value is processed. (The first time the function is called in our example, T is
turned into a Double and the second time into a String.)
In our example, we only use one parameter and therefore the function
can only work with one data type, but we can declare two or more generic
data types separated by commas (e.g., <T, U>).
The main advantage of functions is that we can call them from other parts
of the code and they will always perform the same operations. We don't
even need to know how the function does it, we just send to the function
the values we want to process and read the result. Because of this feature,
functions can be shared, and programmers can implement pre-
programmed functions provided by libraries and frameworks to
incorporate additional functionality that would take them too long to code
themselves.
All the features of the Swift language we have implemented so far are
included in a library called Standard Library. The Standard Library includes
everything, from operators to primitive data types, as well as predefined
functions. The following are some of the most frequently used.
There are also functions available to stop the execution of the application
in case of an unrecoverable error.
Of all the functions in the Swift Standard Library, print() is probably the most
useful. Its purpose is to print messages on the Xcode's console that may
help us fix bugs in our code. In the following example, we use it to print the
result of two operations.
The code in Listing 3-16 implements the abs() function to calculate the
absolute value of -25, then gets the smallest value between absolutenumber
and the number 100 with the min() function, and finally prints a message on
the console with the result.
As we will see later, sequences and collections of values are very important
in computer programming. The strings studied in Chapter 2 are a clear
example. A string is a sequence of values of type Character. The Swift
Standard Library includes a few functions to quickly create sequences of
values our application may need to process information. The following are
some of the most frequently used.
The conditionals and loops studied in Chapter 2 and the functions studied
in this chapter have a thing in common; they all use blocks of code
(statements in braces) to enclose their functionality. Blocks are
independent processing units; they contain their own statements and
variables. To preserve their independence and avoid conflicts between
these units and the rest of the code, their variables and constants are
isolated. Variables and constants declared inside a block are not accessible
from other parts of the code; they can only be used inside the block in
which they were created.
The space in the code where a variable is accessible is called scope. Swift
defines two types of scopes: the global scope and the local scope (also
referred as global space or local space). The variables and constants
outside a block have global scope, while those declared inside a block have
local scope. The variables and constants with global scope are accessible
from any part of the code, while those with local scope are only accessible
from the statements inside the block in which they were created (and the
statements from blocks created inside their block). To better understand
how scopes are defined in code, here is a practical example.
func first() {
let base = 10.0
total += base * multiplier
}
func second() {
let multiplier = 5.0
let base = 3.5
total += base * multiplier
}
first()
second()
This example declares two variables in the global space, multiplier and total,
and two functions with local constants. The multiplier and total variables are
global and therefore they are accessible from anywhere in the code, but
the constants defined inside the functions are available only to the
statements inside the function in which they were created. Therefore, the
base constant declared inside the first() function is only accessible from this
function (neither the statements in the global space nor other functions or
blocks outside first() have access to it), but we can modified the value of total
from this function because it is a global variable.
The next function, second(), declares a new constant called multiplier. This
constant has the same name as the multiplier variable declared before in the
global space, but they have different scopes and therefore they are
assigned different locations in memory and can contain different values.
When we read the value of multiplier in the second() function to add a new
value to the total variable, the multiplier that the system reads is the one
declared inside the function because in that space this constant has
precedence over the global variable with the same name, which shows
that we can declare variables and constants with the same name as long as
they have different scopes.
Closures

This example defines a closure and assigns it to the multiplier constant. After
this, the name of the constant may be used to execute the closure. Notice
that the parameters of the closure and the return type are declared with
the same syntax as functions ((number: Int, times: Int) -> Int), but the
parameters' names are not turned into argument labels and therefore they
are ignored in the call.
An advantage of being able to assign closures to variables is the possibility
to initialize the variables with the result of complex operations. The closure
is assigned to the variable and executed right away adding parentheses at
the end of the declaration. When the system reads the statement, it
executes the closure and then assigns the value returned by the closure to
the constant or variable.
The closure declared in Listing 3-20 doesn't receive any value and returns
an integer (() -> Int). The code in the closure adds the values of a collection
(1 to 9) and returns the result, but because we included the parentheses at
the end of the definition, the value assigned to the myaddition constant is
the one returned by the closure (45), not the closure itself.
If the closure does not receive any parameter, we can simplify the syntax
by declaring the constant's data type to be the same than the data type of
the value returned by the closure. Notice that in this case, the in keyword
can also be removed.
Closures cannot only be assigned to constants and variables but also sent
and returned from functions, as any other value. When a function receives
a closure, the parameter's data type only has to include the data types the
closure receives and returns, as in the following example.
The first statement in Listing 3-22 defines a closure that multiplies two
integers and returns the result. A function that receives a closure of this
type is defined next. Notice that the data type of the value received by the
function was declared as (Int, Int) -> Int. This indicates to the compiler that
the processclosure() function can receive a closure that in turn receives two
integer values and returns another integer. When the processclosure()
function is called in the last statement, the value of the multiplier variable is
sent to the function. The function assigns the closure to the myclosure
constant, and the closure is executed inside the function using this name
and the values 10 and 2, producing the result 20.
The closure in the previous example was defined in the global space and
was executed inside the processclosure() function, but we don't need to assign
the closure to a variable, we can just define it in the function's call.
The code in Listing 3-23 works the same way as the previous example, but
it was simplified by assigning the closure directly to the function's
argument. This can be simplified even further by using a pattern called
Trailing Closures. When the final argument of a function is a closure, we
can declare the closure at the end of the call, as in the following example.
When we pass the closure this way, the call does not include the myclosure
argument anymore. The closure declared after the parentheses is
considered to be the last argument of the function and therefore the
argument label is not necessary.
The code in Listing 3-24 works the same way as previous examples, the
only advantage is the reduction in the amount of code we have to write.
And that can be simplified even further. In the last example we already
removed the return keyword. As explained before, when the content of a
function (or in this case a closure) includes only one statement, the
compiler implies that the value produced by that statement is the one to
return and therefore the return keyword is not required anymore. But when
we are passing the closure to a function, Swift can also infer the data types
of the values received by the closure and therefore we don't have to
declare that either. Instead, we can represent these values using shorthand
argument names. These are special placeholders made up of the $ symbol
and an index starting from 0. The first value received by the closure is
represented by $0, the second value by $1, and so on.
Again, the code is the same, but now the closure is extremely simple.
When it is executed from the processclosure() function, it receives the values
10 and 2 and assigns them to the placeholders $0 and $1, respectively.
Then, it multiplies their values and returns the result.
So far, we have executed the closure received by the function inside the
same function, but there are situations in which a closure must be
executed outside the function. This usually applies to asynchronous
operations, as we will see later. If we want to execute a closure received by
the function from outside the function, we must declare it as an escape
closure. Escape closures are closures that remain in memory after the
execution of the function is over. They are declared preceded by the
@escaping keyword, as in the following example.
In the code of Listing 3-26, we declare a variable called myclosure that stores
a closure that doesn't receive or return any values (() -> Void), and then
assign an empty closure to it. After that, we define a function called
passclosure() that receives an escaping closure and all it does is to assign that
closure to the myclosure variable. Next, we call the passclosure() function with a
closure that prints a value on the console, so now the myclosure variable
contains that closure. Finally, we execute the closure in myclosure and the
message is printed on the console.
To define a new structure, we must use the struct keyword and enclose the
data and functionality in braces.
This example defines a structure called Item with two properties (variables):
name and price. The definition is just delineating the elements of the data
type (also called members), like a blueprint that will be later used to create
the real structures. What we need to do to store values of this new data
type is to declare a variable or a constant, as we did for any other data type
before. In this case, the data type is the name of the structure and the
initialization value is a special initializer with the syntax Name() (where Name
is, again, the name of the structure).
The code in Listing 3-28 creates a variable of type Item that stores an
instance of the Item structure containing the properties name and price. The
instance is created by the Item() initializer and then assigned to the purchase
variable.
In this last example, the properties of a new instance always take the
values declared in the structure’s definition ("Not Defined" and 0), but we can
modify them as we do with any other variable. The only difference is that
the properties are inside a structure, so every time we want to access
them, we must mention the structure they belong to. The syntax
implements dot notation, as in variable.property, where variable is the name of
the variable that contains the instance of the structure and property is the
name of the property we want to access.
In this example, the properties of the Item structure and the purchase
variable are declared as before, but this time we let Swift infer their data
types. After the instance is created, new values are assigned to its
properties using dot notation. Dot notation is not only used to assign new
values but also to read the current ones. At the end, we read and print the
values of the name and price properties on the console ("Product: Lamps $
10.5").
Listing 3-30 defines two structures: Price and Item. The Item structure
contains the same properties as before, but now the data type of the price
property is Price, which means that instead of storing a single value, this
property can now store a structure that in turn contains two properties:
USD and CAD. When the Item structure is created and assigned to the
purchase variable, the Price structure for the price property is also created
with its values by default.
By concatenating the names of the variables and properties we can read
and modify any value we want. For instance, the last statement in the code
of Listing 3-30 accesses the USD property of the price structure inside the
purchase structure to assign a price to the item in American Dollars
(purchase.price.USD = 10.50).
In this example, the price structure is created during instantiation, but this is
not usually the case. Sometimes the values of the properties containing
structures are defined after the instance is created and therefore those
properties must be declared as optionals. The problem with optionals is
that we always need to check whether the variable or property has a value
before we use it. To simplify this task, Swift introduces Optional Chaining.
Optional chaining makes it easy to access properties and methods in a
hierarchical chain that contains optional components. As always, these
components are accessed using dot notation, but a question mark is added
to the names of the properties that have optional values. When the system
finds an optional, it checks whether it contains a value and continues
reading the expression only in case of success. Here is the same example,
but with the price property turned into an optional.
Besides using dot notation to read and write a property, we can use key
paths. A key path is a reference to a property that we can use to read and
modify its value. The advantage of using key paths instead of dot notation
is that they are stored in structures and therefore we can pass them to
other parts of the code and then use them to access the values of the
properties they are referencing without even knowing what those
properties are. This can be useful when we need to interact with
frameworks, or when we are extending code to include our own
functionality.
Swift defines several structures to store key paths. For instance, a read-only
key path is stored in an instance of a structure of type KeyPath and read-
and-write key paths are stored in a structure of type WritableKeyPath.
The syntax to define a key path includes a backward slash and the name of
the data type followed by the name of the property we want to reference.
To access the value of a property using a key path, Swift uses a syntax that
includes square brackets after the instance's name and the keypath keyword,
as illustrated in the following example.
This code defines a structure with two properties: name and price. Next, we
create a key path to reference the price property. Because the properties
are defined as constants, the key path is created of type KeyPath (a read-
only key path). In the last statement, we use this key path to access the
value of the price property of the purchase instance and print it on the
console.
We can easily create a read-and-write key path by defining the structure's
properties as variables. In the following example, we turn the name and price
properties into variables and modify the value of price using our keyPrice key
path.
Listing 3-35: Assigning new values to properties from inside the structure

struct Item {
var name = "Not defined"
var price = 0.0
Every instance created from the structure’s definition has the purpose to
store and process specific data. For example, we can create multiple
instances of our Item structure to store information about different
products. Each product will have its own name and price, so the properties
of each instance must be initialized with the proper values. The
initialization of an instance is a very common task and it would be far too
cumbersome if we had to assign the values one by one every time a new
one is created. For this reason, Swift provides different alternatives to
initialize the values of a structure. One of them is called Memberwise
Initializer.
Memberwise initializers detect the properties of the structure and declare
their names as argument labels. Using these argument labels, we can
provide the values for initialization between the parentheses of the
initializer. The following code implements a memberwise initializer to
initialize an instance of the Item structure declared in previous examples.
The two forms of initialization we have seen so far are not customizable
enough. Some structures may have multiple properties that require
initialization or even methods that must be executed right away to get the
proper values for the instance to be ready. To add more alternatives, Swift
provides a method called init(). The init() method is called as soon as the
instance is created, so we can use it to initialize the properties any way we
want.
init() {
USD = 5
CAD = USD * 1.29
}
}
var myprice = Price()

init(americans: Double) {
USD = americans
CAD = USD * 1.29
}
}
var myprice = Price(americans: 5)

This is similar to what Swift creates for us in the background when we use
memberwise initializers, but the advantage of declaring the init() method
ourselves is that we can specify only the parameters we need (as in the last
example) or even declare multiple init() methods to present several
alternatives for initialization, as shown next.
init(americans: Double) {
USD = americans
CAD = USD * 1.29
}
init(canadians: Double) {
CAD = canadians
USD = CAD * 0.7752
}
}
var myprice = Price(canadians: 5)

By including the set method for the canadians property we can, for
example, set a new price using the same currency.
purchase.canadians = 500
print("Price: \(purchase.USD)") // "Price: 387.6"

The new structure defined in Listing 3-43 can retrieve and set a price in
Canadian dollars. When we set a new value for the canadians property, the
value is stored in a constant called newValue (the constant is created
automatically for us). Using this constant, we can process the new value
and perform the operations we need. In this example, the value of newValue
is multiplied by the exchange rate to get the price in American dollars. The
price is always stored in American dollars but using the canadians property
we can set it and retrieve it in Canadian dollars.
If we want to use a different name for the new value, we can set the
parameter’s name between parentheses. In the following example, the
parameter was called CAD and used instead of newValue to calculate the
value for the USD property.
Listing 3-44: Using a different name for the parameter of the set method

struct Price {
var USD: Double
var ratetoCAD: Double
var ratetoUSD: Double
func description() {
print("The value is: \(myvalue)") // "The value is: 5"
}
}
let instance = MyStructure<Int>(myvalue: 5)
instance.description()

func description() {
print("The value is: \(myvalue)") // "The value is: Hello"
}
}
let instance = MyStructure(myvalue: "Hello")
instance.description()

This is the same as assigning the values directly to the variable (e.g., var
myprice = 4.99), but these initializers become useful when the value we want
to assign to the structure is of different type. The definitions of these
structures include several initializers that convert the value to the right
type. This is called Casting, and we can use it to turn a variable of one data
type into another. For example, when we divide numbers, the system
converts those numbers to the most comprehensive type and performs the
operation, but variables are already of a specific type and therefore they
must be explicitly converted before the operation is performed or we get
an error. (The process does not really convert the variable; it just creates a
new value of the right type.)
The variables number1 and number2 defined in Listing 3-51 are of type Int
and Double. To perform a division between them we must cast one of them
to the data type of the other (arithmetic operations cannot be performed
on values of different data type). Using the Double() initializer, we create a
new value of type Double from the value of number1 and perform the
operation. (The value 10.0 created by the initializer is divided by the value
2.5 of number2 to get the result 4.0.) The process is described as "casting the
number1 variable to a Double".
These initializers are also useful when working with String values.
Sometimes the characters of a string represent numbers that we need to
process. The problem is that strings cannot be processed as numbers. We
cannot include a string in an arithmetic operation without first converting
the string into a value of a numeric data type. Fortunately, the initializers
for numeric types such as Int and Double can convert a value of type String
into a number. If the operation cannot be performed, the initializer returns
nil, so we can treat it as an optional value. In the following example, we
convert the string "45" into the integer 45 and add the value 15 to it.
The structures defined for primitive data types also have their own
properties and methods. This includes type properties and methods. For
instance, the following are the most frequently used properties and
methods provided by the structures that process integer values (e.g., Int).
min—This type property returns the minimum value the data type
can handle.
max—This type property returns the maximum value the data type
can handle.
random(in: Range)—This type method returns a random number.
The value is calculated from a range of integers provided by the in
argument.
negate()—This method inverts the sign of the value.
isMultiple(of: Int)—This method returns true if the value is a
multiple of the value provided by the of argument (this is similar to
what we can achieve with the % operator).
The min and max properties are especially useful because they allow us
to determine whether an operation could overflow a variable (produce a
result that is greater or lesser than the minimum and maximum allowed).
Listing 3-53: Checking the maximum possible value for the Int8 type

var mynumber: Int8 = 120
let increment: Int8 = 10
This example takes advantage of the max property to make sure that
incrementing the value of a variable will not overflow the variable (the
result will not be greater than the maximum value the variable can
handle). The code starts by defining a variable of type Int8 to store the
result of the operation and another to store the number we want to add.
Then, we calculate how far the current value of mynumber is from the
maximum value admitted by an Int8 variable (Int8.max – mynumber) and
compare this result with the value of increment. If the number of units we
have left is greater or equal than the value of increment, we know that the
operation can be performed without going over the limit. (In this example,
the operation is not performed because the addition of 120 + 10 produces
a result greater than the limit of 127 admitted by Int8.)
The type Double also includes its own selection of properties and
methods. The following are the most frequently used.
In this case, the most useful method is probably rounded(). With this
method, we can round a floating-point value to the nearest integer.
Of course, Boolean values are also structures. Among others, the Bool
data type offers the following methods.
The following example checks the current value of a variable and assigns
the value false if it is true, or vice versa.
...(three dots) creates a range from the value on the left to the
value on the right, including both values in the range (e.g., 1...5
creates a range that includes the values 1, 2, 3, 4 and 5). The value
on the right can be omitted to create a one-sided range. A one-
sided range goes from the value on the left to the maximum value
allowed for the data type.
..< (two dots and the less than character) creates a range from the
value on the left to the value before the value on the right (e.g., 1..
<5 creates a range that includes the values 1, 2, 3 and 4).
When we declare a range using these operators, Swift creates the proper
structure according to the operator involved. A structure of type Range is
created for an open range and a structure of type ClosedRange is created for
a closed range. These structures provide common properties and methods
to work with the range. The following are the most frequently used.
switch age {
case 2...4:
message += "Day Care"
case 5...11:
message += "Elementary School"
case 12...17:
message += "High School"
case 18..<22:
message += "College"
case 22...:
message += "Work"
default:
message += "Breastfeeding"
}
print(message) // "You have to go to Elementary School"

while mynumber != 5 {
mynumber = Int.random(in: 1...10)
attempts += 1
}
print("It took \(attempts) attempts to get the number 5")

String Structures

Not only primitive data types and ranges are structures, but also the rest of
the data types defined in the Swift Standard Library, including the String
data type. As we have seen in Chapter 2, we can initialize a String structure
by simply assigning a string (a text between double quotes) to a constant
or a variable. This is another shortcut. In the background, instances of the
String structure are created from the initializer included in the structure’s
definition.
Once the string is created, we can manipulate it with the properties and
methods provided by the String structure. The following are the most
frequently used.
The first thing we do in this example is to check the value of the isEmpty
property to make sure the string is not empty and there are characters to
read (notice the ! operator to invert the condition). Once we know that
there are characters to work with, we get the index of the string’s first
character from the startIndex property and read the character in that
position using square brackets.
If we want to access a character in a different position, we must increment
the value returned by startIndex. The trick is that, since Index values are not
integers, we cannot just add a number to them. Instead, we must use the
methods provided by the String structure.
The index() method applied in Listing 3-62 takes an integer to calculate the
new index. The original index is incremented the number of units indicated
by the integer and the resulting Index value is returned. With this index, we
get the character at the position 6 (indexes start from 0).
If we wanted to get the previous index, we could have specified a negative
number of units for the offset value, but another way to move forward and
backward is to implement the other versions of the index() method. The
following example gets the next index after the initial index and prints the
corresponding character on the console.
Once the right index is calculated, we can call some of the String methods
to insert or remove characters. The insert() method, for instance, inserts a
single character at the position indicated by the second argument. In the
following example, we call it with the value of endIndex to add a character at
the end of the string (endindex points to the position after the last
character).
If we do not know where the character is located, we can find the index
with the firstIndex() method. The value returned by this method is an
optional containing the Index value of the first character that matches the
argument or nil if no character is found. In the following example, we
implement it to find the first space character and remove it with the
remove() method.
Listing 3-65: Removing a character

var text = "Hello World"
var findIndex = text.firstIndex(of: " ")
The firstIndex() method in Listing 3-66 looks for a space character and
returns its index. With this value, we can create a range from the first
character to the space character and get the first word. But we must be
careful because the end index is pointing to the space character, not to the
last character of the word. To get the word without the space, we create an
open range with the..< operator, so the character on the right is not
included.
We can also use ranges to replace or remove parts of the text. The String
structure offers the replaceSubrange() and removeSubrange() methods for this
purpose.
Listing 3-67: Working with ranges of characters

var text = "Hello World"
var start = text.startIndex
var findIndex = text.firstIndex(of: " ")
The replaceSubrange() method in Listing 3-67 replaces the characters from the
beginning of the string up to the character before the space character
("Hello") with the string "Goodbye", and the removeSubrange() method uses
an open range to remove the characters of this sentence from the space
character to the end of the string (" World"), getting the final string
"Goodbye". Notice that after applying the methods over the same string,
the indexes are lost and therefore they must be recalculated. That's why
before calling the removeSubrange() method we search for the position of the
space character once more and update the findIndex variable.
The rest of the methods provided by the String structure are
straightforward. For instance, the following example implements two of
them to check if a string contains the word "World" at the end and
converts all the letters into uppercase letters.
if text.hasSuffix("World") {
print(text.uppercased()) // "HELLO WORLD"
}

Array Structures

The strings studied before and the values we have created in previous
examples with functions such as stride() or repeatElement() are collections of
values. Collections do not represent a value; they are containers for other
values. A value of type String does not contain the string "Hello", it contains
a collection of variables of type Character, with the values H, e, l, l, and o.
Swift includes several collections like this, some were defined to contain
specific values, like String, and others are generic (they can store values of
any data type we need). One of those collections is Array.
Arrays are collections that contain an ordered list of values. They are
generic structures that have the capacity to store all the values we need of
any data type we want, but with the condition that once a data type is
selected, all the values must be of that same type. For example, if we
create an array of type Int, we will only be able to store values of type Int in
it. Swift offers multiple syntaxes to create an array, including the following
initializers.
As with any other variable, Swift can infer the data type from the values.
The list array declared in this example is initialized with three integer
values, 15, 25 and 35. The values of an array are usually called elements or
items. On these terms, we can say that the code in Listing 3-70 declares an
array of three elements of type Int.
An index is automatically assigned to each value starting from 0, and as
with strings, we must specify the index of the value we want to read
surrounded by square brackets.
The last statement in Listing 3-71 prints the value of the second element of
the list array on the console (the element at index 1). We can also use
indexes to modify the values.
Assigning new values is only possible for elements that already exist in the
array. If we try to access an element with an index that doesn't exist, we
get an error. One way to add a new element, or several, is with the +=
operator.
The += operator adds an array at the end of another array. In Listing 3-73,
we use it to add two more elements to the array declared in the first
statement. The += operator concatenates the two arrays and assigns the
result back to the same variable. If we want to use two or more arrays to
create a new one, we can apply the + operator.
To remove all the elements from an array, we can assign to the variable
one of the initializers introduced before or just the square brackets with no
values.
Arrays are collections of values and therefore we can iterate over their
values with a for in loop, as we did with strings before.
The code in Listing 3-78 implements a for in loop to add the numbers in the
list array to the total variable. At the end, we print the result. Although this is
a legit way to do it, arrays offer multiple properties and methods to read
and process their values.
In the previous example, we have seen how to iterate over the elements of
an array with the for in loop, but that iteration only returns the value of the
element, not its index. An alternative is provided by the enumerated()
method, designed to work with these types of loops. Each cycle returns a
tuple with the index and the value of the current element.
This example uses the constants myindex and myfruit to capture the values
produced by the enumerated() method and generates a string. Notice that
since the array’s indexes start from 0, we added 1 to myindex to start
counting from 1.
Another useful property is count. As mentioned before, we can access each
element of the array with the index between square brackets. But trying to
read a value in an index that has not yet been defined will produce an
error. To make sure that the index exists, we can check whether it is greater
than 0 and less than the total amount of elements in the array using this
property.
We can also select a random value with the randomElement() method. This
method selects a value from the array and returns an optional, so we must
compare it against nil or use optional binding before processing it, as in the
following example.
Besides working with all the elements of an array, we can do it with a range
of elements.
This example gets the elements at the indexes 0 and 1 from the fruits array
and assigns them to the new someFruits array. Now we have two arrays: fruits
with 4 elements and someFruits with 2.
Arrays created from a range of indexes are of type ArraySlice. This is another
collection type provided by Swift to store temporary arrays that are
composed of elements taken from other arrays. We can iterate over these
types of arrays with a loop or read its elements as we do with normal
arrays, but if we want to assign them to other array variables or use them
for persistent storage, we must cast them as Array types using the Array()
initializer. The initializer takes the values in the ArraySlice variable and
returns a normal array, as shown next.
The filter() method sends the values one by one to the closure, the closure
replaces the placeholder ($0) with the current value, compares it with the
value "Grape", and returns a Boolean with the result. If the value is true, the
element is included in filteredArray.
If what we need is to modify the elements of an array all at once, we can
use the map() method. This method sends to a closure the values of the
array one by one and returns another array with the results produced by
the closure.
The example in Listing 3-91 defines a list of integers and then calls the map()
method on the array to divide each value by 2. The map() method sends the
values of the array to the closure one by one, the closure replaces the
placeholder ($0) with the current value, divides the number by 2, and
returns the result. All the results are stored in a new array and that array is
returned by the map() method when the process is over.
Of course, we can perform any kind of operations we want on the values in
the closure. For instance, the following code converts the values into
strings with the String() initializer.
This example produces the same result as before, but instead of using a
closure, we use a reference to the String initializer. The map() method sends
the value to the initializer, the initializer returns a new String structure with
that value, and the process continues as always.
Another way to process all the values of an array at once is with the reduce()
method. This method works like map(), but instead of storing the results in
an array, it sends the result back to the closure to get only one value in
return. For instance, the following code uses the reduce() method to get the
result of the addition of all the numbers in an array.
The code in Listing 3-94 defines an array of integers and then calls the
reduce() method on it. This method sends two values at a time to the
closure. In the first cycle, the values sent to the closure are the ones
provided by the first argument (0) and the first value of the array (2). In the
second cycle, the values sent to the closure are the value returned by the
closure in the first cycle (0 + 2 = 2), and the second value of the array (4).
The loop goes on until all the values of the array are processed.
When it comes to sorting the elements of an array, there are several
options available. The most frequently used are reversed() and sorted() (and its
variant sorted(by:)). The reversed() method takes the elements of an array and
returns a new array with the same elements in reversed order. The value
returned by the method is stored in a structure of type ReversedCollection. As
we did before with the ArraySlice type, we can cast these values as Array
structures with the Array() initializer.
The sorted() method sorts the array in ascending order and returns a new
array.
If we want to sort the elements in a custom order, we can use the sorted(by:)
method. This method works in a similar fashion to the filter() method
studied before. It takes a function or a closure that receives the value of
two elements and returns true if the first element should appear before the
second element, or false otherwise.
Arrays also include two powerful methods to compare elements: min() and
max(). These methods compare the values and return the smallest or
largest, respectively.
The code in Listing 3-100 takes the largest value from an array of integers
and counts the number of digits in the value returned. Because the max()
method returns an optional, we use optional binding to read the value. The
rest of the code turns this value into a string and counts its characters to
print the number of digits on the console.
Besides selecting the largest or smallest value with the max() and min()
methods, we can also fetch values from the array using the first and last
properties.
In this example, we look for the index of a value smaller than 30. The
firstIndex(where:) method reads every value of the array from the beginning
and sends them to the closure assigned to the where argument. The closure
takes the current value returned by the placeholder and compares it
against the number 30, if the value is greater than 30, the closure returns
false, otherwise it returns true and the index of that value is assigned to the
first variable. In this case, the first number in the array smaller than 30 is
12, and therefore the index assigned to the variable is 2.
Set Structures

This initializer can be used to create an empty set (e.g., let myset = Set<Int>()),
but we can also use square brackets, as we do with arrays. The difference
with arrays is that we must specify that we are creating a set with the Set
keyword, as shown next.
If we initialize the set with some values, Swift can infer its type from their
data type, simplifying the declaration.
Using these methods, we can easily access and modify the values of a
set. For instance, we can implement the contains() method to search for a
value.
if fruits.contains("Apple") {
print("Apple exists!")
}

if !fruits.contains("Grape") {
fruits.insert("Grape")
}
print("The set has \(fruits.count) elements") // 4

In listing 3-107, we use the contains() method again to check if an
element with the value "Grape" already exists in the set. But this is not
really necessary. If the value is already part of the set, the insert() method
does not perform any action.
To remove an element, we must call the remove() method.
The remove() method removes the element which value matches the
value of its argument and returns an optional with the value that have
been removed or nil in case of failure. In the code of Listing 3-108, we get
the value returned by the method and print a message if the value was
successfully removed.
Sets are collections without order. Every time we read a set, the order in
which its values are returned is not guaranteed, but we can use the sorted()
method to create an array with the values of the set in order. The following
example sorts the elements of the fruits set in alphabetical order, creating a
new array we call orderFruits.
if basket.isSubset(of: store) {
print("The fruits in the basket are from the store")
}

Dictionary Structures

There is only one way to access the elements of an array and that is
through their numeric indexes. Dictionaries offer a better alternative. With
dictionaries, we can define the indexes ourselves using any custom value
we want. Each index, also known as key, must be explicitly declared along
with its value. Swift offers multiple syntaxes to create a dictionary,
including the following initializers.
If the data types are explicitly defined, we can also declare a dictionary
with a simplified syntax, as in var list: [String: String] = Dictionary(), or use square
brackets with a colon, as in var list: [String: String] = [:]. The latest is also used
to define a dictionary with initial values. In this case, the keys and values
are separated by a colon and the items are separated by comma, as in the
following example.
Listing 3-112: Declaring a dictionary with initial values

var list: [String: String] = ["First": "Apple", "Second": "Orange"]

The first value of each item is the key and the second is the value. Of
course, if the keys and the values are of a clear data type, Swift can infer
them.
The second statement in Listing 3-114 assigns a new value to the element
identified with the "Second" key. Now, the dictionary contains two
elements with the values "Apple" and "Banana". If the key used to assign
the new value exists, the system updates the value, but if the key does not
exist, a new element is created, as shown next.
In this example, the second statement assigns the value "Banana" to a key
that does not exist, and therefore the system creates the new element
with the specified key and value.
Dictionaries return optional values. If we try to read an element with a key
that does not exist, the value returned is nil.
The code in Listing 3-116 tries to read a value with the "Third" key in the list
dictionary that does not exist. As a result, the value nil is printed on the
console. If the element exists and we want to read its value, we must
unwrap it.
Since dictionary elements are optionals, we can assign the value nil to
remove them. The following example removes the element with the "First"
key.
As with arrays and sets, we can also iterate over the values of a dictionary
with a for in loop. The value produced by each cycle of the loop is a tuple
containing the element’s key and value.
The for in loop in Listing 3-119 reads the elements of the fruits dictionary
one by one, assigns the index and the value to the mykey and myfruit
constants, and adds their values to the message variable. At the end, we get
a string with all the keys and values in the dictionary.
Of course, dictionaries may also contain arrays as values. The declaration is
simple, the key is declared as always, and the single value is replaced by an
array.
In this example, we create a dictionary with two values. The values are
arrays of strings with a string as key. The code gets the array corresponding
to the "A" key, unwraps it, and stores it in a constant. The list constant now
contains the array assigned to the "A" key, and therefore when we read the
element at index 0, we get the value "Apple".
What we have created in the last example is what the Dictionary(grouping:, by:)
initializer does. It takes the values of a collection and groups them together
in arrays according to the value of a key returned by the closure, as shown
next.
The Dictionary initializer implemented in Listing 3-122 takes the values of the
list array, sends them to the closure one by one, and creates a new
dictionary with the keys returned by the closure. The closure receives the
value and returns the strings "Yes" or "No" depending on whether the
current value is multiple of 5. If the value is multiple of 5, it is included in
an array with the "Yes" key, otherwise it is included in an array with the
"No" key.
Dictionaries also include properties and methods to manage the values.
The following are the most frequently used.
Some of the methods provided by the Dictionary structure are like those
included in the Array and Set structures, but others are more specific. For
example, the updateValue() and removeValue() methods require the element's
key to process the values.
As with arrays, the sorted() method sends to the closure two values at a
time, but the values in a dictionary are sent as tuples containing the key
and value of each element. For instance, the first values sent to the closure
in Listing 3-124 are ("one", "Banana") and ("two", "Apple"). These values
replace the placeholders $0 and $1, so if we want to order the elements
according to the names of the fruits, we must compare the values of the
tuples at index 1 ($0.1 < $1.1). The array returned is a collection of tuples in
alphabetical order, with every element containing the keys and values of
the dictionary ([(key: "two", value: "Apple"), (key: "one", value: "Banana"), (key:
"three", value: "Pear")]).
Earlier, we saw how to iterate over the elements of a dictionary with a for in
loop (see Listing 3-119). The loop gets each element and generates a tuple
with the key and value. But there are times when we only need the
element’s key or the element’s value. The Dictionary structure provides two
properties for this purpose: keys and values. These properties return a
collection containing only the keys or the values of the elements,
respectively.
The collections returned by the keys and values properties are structures of
type Keys and Values defined inside the Dictionary structure. As we did before
with other collection types, we can turn them into arrays with the Array()
initializer.
Variables of this data type can only store the values allowed by the type
(one, two, or three). To assign a value, we must use the name of the
enumeration and dot notation. The mynumber variable declared in Listing 3-
129 is of type Number and has the value one.
Once the data type of the variable was already defined, only the dot and
the value are necessary to modify its value.
switch mynumber {
case .one:
print("The number is 1")
case .two:
print("The number is 2") // "The number is 2"
case .three:
print("The number is 3")
}

The cases of an enumeration can have values by default. These values are
called Raw Values. Swift assigns values by default to every case, starting
from 0, but we can assign our own.
if mynumber == .two {
print("Correct Value") // "Correct Value"
}

We can read the case value or the raw value to identify an instance of an
enumeration type. In Listing 3-134, we create an instance of Number with
the raw value "Number Two" and then check that the variable contains the
proper case value with an if statement.
What makes enumerations part of the programming paradigm proposed by
Swift is not their capacity to store different types of values but the
possibility to include custom methods and computed properties. The
following example adds a method to our Number enumeration that prints a
message depending on the instance's current value.
When we need to check the current value of the instance from inside a
method, we must use the self keyword. This keyword refers to the instance
where the method is being executed (in our case, mynumber), and this is
how we can check for the instance’s current value and return the right
message. (We will learn more about the self keyword later.)
Associated Values

switch character {
case .number(let value, let description):
print("\(description) - \(value)") // "Number One - 1"
case .letter(let letter, let description):
print("\(description) - \(letter)")
}

This syntax includes the case keyword and the necessary constants to
receive the values. The statement is saying something like "Assign the
value to this case, if not possible, return false". In our example, if the
character variable doesn't contain a MyCharacters enumeration with the value
number, the statement returns false and nothing is done, otherwise, the
associated values in the character variable are assigned to the constants and
printed on the console.
3.5 Objects

Objects are data types that encapsulate data and functionality in the form
of properties and methods, but unlike the structures and enumerations
introduced before they are stored by reference, which means that more
than one variable can reference the same object in memory.
Definition of Objects

Like structures and enumerations, objects are defined first and then
instances are created from their definition. The definitions of objects are
called Classes, and what we called objects are the instances created from
those classes. Classes are declared the same way as structures or
enumerations, but instead of the struct or enum keywords we must use the
class keyword.
This example defines a simple class called Employee with two properties:
name and age. As always, this does not create anything, it is just defining a
new custom data type. To store data in memory in this format, we must
assign an instance of this class to a constant or variable.
This example defines an Employee class with two properties: name and age.
The type method declared next is just describing the purpose of the class.
Every time the description() method is executed on the class, a description is
printed on the console. Again, we don't have to create an instance because
the method is executed on the class itself.
IMPORTANT: Classes can also use the static keyword to define type
properties and methods. The difference between the static and class
keywords is that properties and methods defined with the static
keyword are immutable and those defined with the class keyword can
be modified by subclasses. (We will learn about subclasses and
inheritance later in this chapter.)
Reference Types

Structures and enumerations are value types. This means that every time
we assign a variable of any of these data types to another variable, the
value is copied. For example, if we create an instance of a structure and
then assign that instance to another variable, we end up with two
instances of the same structure in memory, as illustrated below.
Figure 3-1 shows how two different copies of the Employee structure, one
referenced by the employee1 variable and the other referenced by the
employee2 variable, are stored in memory. Any modification to the values of
one of the instances will not affect the other, because they occupy
different spaces in memory.
Objects, on the other hand, are passed by reference. This means that when
we assign an object to constants or variables, they receive a reference to
the object, not the object itself. In the following example, the object in
employee2 is the same as the object in employee1. Any change in the name
property is reflected in the other because both variables point to the same
object in memory (they reference the same instance).
The self keyword in the changename() method of Listing 3-142 represents the
object created from the Employee class and helps the system understand
what we are trying to access when we use the word name. When we call the
changename() method in the employee1 object, the value of the name parameter
is assigned to the object's name property (self.name).
The self keyword in this example is a reference to the object stored in the
employee1 variable. This would be the same as declaring employee1.name, but
since we do not know the name of the variable that is going to store the
instance when the class is defined, we must use self instead.
Another useful application of the self keyword is to reference the data type
itself. The value generated by reading the self keyword on a data type is
called Metatype. A metatype refers to the type itself, not an instance of it.
For example, the value Int.self refers to the definition of the Int data type,
not an integer number created from that type, as shown in the following
example.
The code in Listing 3-143 stores a reference to the Int data type in a
constant and then uses that constant to create an instance of Int with the
value 20. Notice that when working with metatypes we must call the init()
method implicitly to create an instance. Metatypes are widely used to pass
references of data types to methods and initializers of other types, as we
will see in further chapters.
Memory Management

employee?.name = "John"
employee?.location = department
department?.area = "Mail"
department?.person = employee

This example defines two classes: Employee and Department. Both classes
contain a property that references an object of the other class (location and
person). After the definition, objects of each class are created and stored in
the employee and department variables. The reference in the department variable
is assigned to the location property of the employee object, and the reference
in the employee variable is assigned to the person property of the department
object. After this, each object contains a reference to the other.
In this example, we assume that the value nil was assigned to the employee
and department variables, and in consequence the objects are not accessible
anymore, but they are preserved in memory because ARC has no way to
know that they are no longer required.
Swift solves this problem by classifying the references into three
categories: strong, weak, and unowned. Normal references are strong;
they are always valid and the objects associated to them are preserved in
memory for as long as they exist. These are the kind of references we have
been using so far, and that is why the cycle created by our example is called
Strong Reference Cycle. The solution to break this cycle is to define one of
the references as weak or unowned. When ARC encounters one of these types
of references to be the last reference to an object, the object is erased
from memory as if the reference had never existed.
employee?.name = "John"
employee?.location = department
department?.area = "Mail"
department?.person = employee

In the code of Listing 3-145, the person property was declared as weak. Now,
when the references from the variables are erased, the object created
from the Employee class is erased from memory because the only reference
left is the weak reference from the person property. After this object
disappears, the object created from the Department class does not have any
other strong reference either, so it is also erased from memory.
The unowned reference works the same way, but it differs from the weak
reference on the type of values it applies to. Weak references apply to
variables with optional values (they can be empty at some point) and
unowned references apply to non-optional values (they always have a
value).
The Employee class declared in Listing 3-146 is a normal class, like those
we have defined before. It has two properties and a method called
createbadge() that returns a string with the values of the properties. This class
would be enough to create objects that generate the string of text
necessary to print a badge for every employee with his or her name and
age. But for the sake of argument, let’s say that some of the employees
require a badge that also displays the department they work in. One option
is to define another class with the same properties and methods and add
what we need, but this produces redundant code, and it is difficult to do
when the class was taken from a library (they are usually not accessible or
too complex to modify or duplicate). The solution is to create a new class
that inherits the characteristics of the basic class and adds its own
properties and methods to satisfy the new requirements.
To indicate that a class inherits from another class, we must write the
name of the basic class after the name of the new class separated by a
colon.
The OfficeEmployee class added to our code in Listing 3-147 only has one
property called department, but it inherits the name and age properties, and
also the createbadge() method from the Employee class. All these properties
and methods are available in any of the objects created from the
OfficeEmployee class, as shown next.
A class like Employee is called Superclass, and a class that inherits from
another class like OfficeEmployee is called Subclass. In these terms, we can
say that the OfficeEmployee class is a subclass that inherits the properties and
methods of the Employee superclass. A class can inherit from a superclass
that already inherited from another superclass in an infinite chain. When a
property is accessed, or a method is called, the system looks for it on the
object’s class and, if it is not there, it keeps looking in the superclasses up
the hierarchical chain until it finds it.
IMPORTANT: Inheritance does not work the other way around. For
example, considering the code in Listing 3-148, objects created from
the class OfficeEmployee have access to the department property of this
class and the properties and methods of the Employee class, but
objects created from the Employee class do not have access to the
department property.
Because of this hierarchical chain, sometimes a method does not have
access to all the properties available to the object. For example, the
createbadge() method called on the employee object created in Listing 3-148
have access to the properties declared on the Employee class but not those
declared in the OfficeEmployee class. If we want the method to also print the
value of the department property, we must implement it again in the
OfficeEmployee class with the appropriate modifications. This is called
Overriding. To override a method of a superclass, we prefix it with the
override keyword.
This is the same as the previous example, but now, when the
createbadge() method of an object created from the OfficeEmployee class is
called, the method calls the createbadge() method of the superclass first and
assigns the result to the oldbadge constant. The value of this constant is later
added to the value of the department property to generate the final string.
Type Casting

Inheritance not only transfers functionality from one class to another but
also connects the classes together. The superclasses and their subclasses
are linked together in a hierarchical chain. Because of this, whenever we
declare a variable of the type of the superclass, objects of the subclasses
can be assigned to that variable too. This is a very important feature that
allows us to do things like creating arrays of objects that are of different
classes but belong to the same hierarchy.
This example defines a superclass called Employee and then two subclasses
of Employee called OfficeEmployee and WarehouseEmployee. The purpose is to have
the information for every employee in one class and then have classes for
specific types of employee. Following this organization, we can create
objects that contain the name, age, and deskNumber properties to represent
employees working at the office and objects that contain the name, age, and
area properties to represent employees working at the warehouse.
No matter the differences between one object and another, they all
represent employees of the same company, so sooner or later we will have
to include them on the same list. The class hierarchy allows us to do that.
We can declare a collection of the data type of the superclass and then
store objects of the subclasses in it, as we did in Listing 3-151 with the list
array.
This is all good until we try to read the array. The array was declared of
type Employee, so we can only access the properties defined in the Employee
class. Also, there is no way to know what type of object each element is.
We could have an OfficeEmployee object at index 0 and later replace it with a
WarehouseEmployee object. The indexes do not provide any information to
identify the objects. Swift solves these problems with the is and as
operators.
In Listing 3-152, we create the list array again with objects from the
same classes defined in the previous example, but this time we add a for in
loop to iterate over the array and count how many objects of each class we
have found. The if statement inside the loop implements the is operator to
check if the current object stored in the obj constant is of type OfficeEmployee
or WarehouseEmployee and increments the counter respectively (countOffice or
countWarehouse).
Counting objects is not really what these operators are all about. The
idea is to figure out the type with the is operator and then convert the
object with the as operator to be able to access their properties and
methods. The as operator converts a value of one type to another. The
conversions are not always guaranteed, and that is why this operator
comes in two more forms: as! and as?. These versions of the as operator
work like optionals. The as! operator forces the conversion and returns an
error if the conversion is not possible, and the as? operator tries to convert
the object and returns an optional with the new object or nil in case of
failure.
In this example, we use optional binding to cast the object and assign
the result to the temp constant. First, we try to cast obj as an OfficeEmployee
object. If we are successful, we assign the value 100 to the deskNumber
property, but if the value returned is nil, then we try to cast the object to
the WarehouseEmployee class and modify its area property.
Casting can also be performed on the fly if we are sure that the
conversion is possible. The statement to cast the object is the same but it
must be declared between parentheses.
In this example, we do not assign the object to any variable; we just cast
the element of the list array at index 1 as a WarehouseEmployee object inside
the parentheses and then access its area property. The value of this
property is stored in the myarea constant and then printed on the console.
All we need to remember is that conversions performed with the as!
operator are only possible when we are sure it is going to be successful.
The list array declared in Listing 3-156 is of type AnyObject and therefore
it can contain objects of any data type. To populate the array, we created
two simple and independent classes: Employee and Department. A few objects
are created from these classes and included in the array. The objects are
later casted by the as? operator inside a for in loop and their corresponding
properties are modified following the same procedure used in previous
examples.
Initialization

The init() method declared for the Employee class in Listing 3-157 initializes
every property of the class with the values specified in the Employee()
initializer. This type of initializer is called Designated Initializer. When we
declare a Designated Initializer, we need to make sure that all the
properties are initialized.
If we know that in some circumstances our code will not be able to provide
all the values during initialization, we can also declare a Convenience
Initializer. A Convenience Initializer is an initializer that offers a convenient
way to initialize an object with values by default for some or all of its
properties. It is declared with the init() method but preceded by the
convenience keyword. A Convenience Initializer must call the Designated
initializer in the class with the corresponding values.
Listing 3-158: Declaring a Convenience Initializer

class Employee {
var name: String
var age: Int
The code in Listing 3-159 defines the subclass OfficeEmployee that inherits
from the Employee class. The OfficeEmployee class does not provide any
initializer, so the only initializer available is the one provided by its
superclass. This initializer only initializes the properties name and age. The
department property of OfficeEmployee is explicitly initialized with the value
"Undefined". To provide an initializer that also includes this property, we
must declare a new Designated Initializer in the OfficeEmployee class.
deinit {
print("This instance was erased")
}
}
var purchase: Item? = Item()
purchase = nil

The lazy keyword is frequently used when the property’s value may take
time to be determined and we don't want the initialization of the structure
or the class to be delayed. For example, we may have a property that
stores a name retrieved from a server, which is a resource intensive task
that we should perform only when the value is required.
return "Undefined"
}()
var age = 0
}
let employee = Employee()

The Employee class in Listing 3-162 defines two properties, name and age, but
this time a closure is assigned to the name property to get the employee's
name from a server (we will see how to retrieve information from the Web
in Chapter 17). Because we declared this property as lazy, the closure will
only be executed when we try to read it. If we execute the example as it is,
we get nothing in return, but if we read the name property with a statement
at the end, we will see the text "Loading..." printed on the console.
The Swift language also includes keywords to define the level of access for
each entity in our code. Access control in Swift is based on modules and
source files, but it also applies to single properties and methods. Source
files are the files we create for our application, the properties and methods
are the ones we have created for our structures and classes in previous
examples, and modules are units of code that are related with each other.
For instance, a single application and each of the frameworks included in it
are considered modules (we will introduce frameworks in Chapter 4).
Considering this classification, Swift defines five keywords to determine
accessibility.
As we will see later, most of these keywords apply to frameworks and are
rarely used in single applications. By default, the properties and methods
we include in our classes and structures are declared internal, which means
that our classes and structures are only available from inside our
application (module). Unless we are creating our own frameworks, this is
all we need for our applications, and is the reason why we didn't have to
specify any keyword when we defined our structures and classes before.
All our classes and structures are accessible by the rest of the code inside
our application, but if we want to have a level of control in our data types
or avoid modifying values by mistake, we can declare some of them as
private, as shown next.
func showValues() {
print("Name: \(name)")
print("Age: \(age)")
}
}
let employee = Employee()
employee.showValues()

The code in Listing 3-163 defines the name and age properties of our Employee
class as private and adds a method called showValues() to access their values.
Due to access control, these properties are only accessible by the method
in the class. If we try to read their values from outside the object using dot
notation, Xcode will return an error (employee.name).
If what we want is to be able to read the property from outside the object
but not allow assigning new values to it, we can declare it with a public
getter but a private setter.
The age property in the Employee class of Listing 3-164 was declared as
public, so everyone can read its value, but with a private setter (private(set)),
so only the methods inside the class can modify it. To change the value, we
defined the setAge() method. The code creates an instance of the class and
calls the method, but this time we can read the value of the age property
and print it on the console because it was declared with a public getter.
Singletons

The Employee class in this example defines a type property called shared
and initializes it with an instance of the same class, so every time we
read this property from anywhere in the code, we get the same Employee
object in return. For instance, in this example, we define two constants
called employee1 and employee2, and initialize them with the instance
returned by the shared property. Because this is the same object, when
we modify a value in the employee1 object, the same value is returned by
the employee2 object.
Because the purpose of this class is to only allow the creation of one
instance (one object), we prefix the initializer with the private keyword.
As a result, only the single instance created by the shared property is
available. That is the reason why the object created from this class is
called Singleton.
3.6 Protocols

The main characteristics of classes, and therefore objects, are the capacity
to encapsulate data and functionality and the possibility to share and
improve code through inheritance. This introduced an advantage over
previous paradigms and turned the Object-Oriented Programming
paradigm into the industry standard for a while. But that changed with the
introduction of protocols in Swift. Protocols define properties and methods
that structures can have in common. This means that Swift’s structures not
only can encapsulate data and functionality, just like objects, but by
conforming to protocols they can also share code. Figure 3-5 illustrates the
differences between these two paradigms.
In OOP, the code is implemented inside a class and then objects are
created from that class. If we need to create objects with additional
functionality, we must define a subclass that inherits the code from the
superclass and adds some of its own. Protocols offer a slightly different
approach. The properties and methods we want the structures to have in
common are defined in the protocol and then implemented by the
structures’ definitions. This allows us to associate different structures
together through a common pattern. The code implemented by each
structure is unique, but they follow a blueprint set by the protocol. If we
know that a structure conforms to a protocol, we can always be sure that
besides its own definitions, it will also include the properties and methods
defined by the protocol. In addition, protocols can be extended to provide
their own implementations of the properties and methods we want the
structures to have in common, allowing the paradigm to completely
replace classes and objects.
Protocols are defined with the protocol keyword followed by the name and
the list of properties and methods between braces. No values or
statements are assigned or declared inside a protocol, only the names and
the corresponding data types. Because of this, methods are defined as
always, but they omit the braces and the statements, and properties must
include the get and set keywords between braces to indicate whether they
are read-only properties, or we can read and assign values to them (see
Listing 3-43 for an example of getters and setters). To indicate that the
structure conforms to the protocol, we must include the protocol’s name
after the structure’s name separated by a colon, as shown in the following
example.
func printdescription() {
print("Description: \(name) \(age)") // "Description: John 32"
}
}
let employee1 = Employees(name: "John", age: 32)
employee1.printdescription()

A protocol tells the structure what properties and methods are required,
but the structure must implement them. In the example of Listing 3-166,
we define a protocol called Printer that includes the name property and the
method. The Employees structure defined next conforms to
printdescription()
this protocol, and along with the protocol’s property and method it also
implements its own property called age. Although this property was not
defined in the protocol, we can read it inside the printdescription() method
and print its value.
The advantage of this practice is evident when structures of different types
conform to the same protocol, as shown in the following example.
func printdescription() {
print("Description: \(name) \(age)")
}
}
struct Offices: Printer {
var name: String
var employees: Int
func printdescription() {
print("Description: \(name) \(employees)") // "Description: Mail 2"
}
}
let employee1 = Employees(name: "John", age: 32)
let office1 = Offices(name: "Mail", employees: 2)
office1.printdescription()

Although the structures created in Listing 3-167 from the Employees and
Offices definitions are different (they have different properties), they both
conform to the Printer protocol and provide their own implementation of
the printdescription() method. The common functionality defined by the
protocol ensures that no matter what type of structure we are working
with, it will always have an implementation of printdescription().
Protocols are also data types. This allows us to associate structures by the
common functionality.
Listing 3-168 uses the same protocol and structures defined in the previous
example, but this time it stores the instances in an array. The type of the
array was defined as Printer, which means the array may contain structures
of any type as long as they conform to the Printer protocol. Because of this,
no matter the element’s data type (Employees or Offices) we know that they
always have an implementation of the name property and the printdescription()
method.
When we process a structure or an object as a protocol type, we can only
access the properties and methods defined by the protocol. If we need to
access the instance's own properties and methods, we must cast it using
the as operator as we did with classes before. The following example prints
the value of the age property if the element of the array is of type Employees.
Because protocols are data types, we can use them to define variables, or
return them from functions. The following example declares a function
that returns a value of type Printer.
Protocols can also define generic properties and methods, but they work
slightly different than the generic types studied before. When we want to
define a protocol with a generic property or method, we first must define
the name of the generic type with the associatedtype keyword.
The Swift language makes use of protocols extensively. Almost every API
includes protocols that define common features and behavior. But there
are also important protocols defined in the Swift Standard Library that we
can use to improve our custom data types. The following are the most
frequently used.
Listing 3-173: Letting the compiler create the protocol methods for us

struct Employees: Equatable {
var name: String
var age: Int
}
let employee1 = Employees(name: "John", age: 32)
let employee2 = Employees(name: "George", age: 32)
Protocols only define the properties and methods that the data types will
have in common, but they do not include any implementation. However,
we can implement properties and methods that will be common to all the
data types that conform to the protocol by taking advantage of a feature of
the Swift language called Extensions. Extensions are special declarations
that add functionality to an existing data type. We can use them with
structures, enumerations, and classes, but they are particularly useful with
protocols because this is the way protocols can provide their own
functionality. The syntax includes the extension keyword followed by the
name of the data type we want to extend. The following example recreates
the Printer protocol introduced in previous examples and extends it with a
method.
In this example, we define a Printer protocol with just the name property and
then extend it to include a common implementation of the printdescription()
method. Now, the Employees and Offices structures in our example share the
same implementation and produce the same result when their
printdescription() methods are executed.
As we already mentioned, extensions are not only available for protocols
but also for any other data type. We can use them to extend structures,
enumerations, and classes. This is particularly useful when we do not have
access to the definitions of the data types and need to add some
functionality (like when they are part of a library or a framework). In the
following example, we extend the Int structure to provide a method that
prints a description of its value.
The Int data type is a structure defined in the Swift Standard Library.
We cannot modify its definition, but we can extend it to add more
functionality. In this example, we add a method called printdescription() to
print a message with the current value. (Notice the use of the self keyword
to refer to the instance.) This method is not included in the original
definition, but it is now available in our code.
Of course, we can also extend our own data types if we consider it
appropriate. The following example extends our Employees structure to add
a new method.
func generatereport() {
delegate.showMoney(name: name, money: money)
}
}
let salary = Salary()
var employee1 = Employees(name: "John", money: 45000, delegate: salary)
func generatereport() {
delegate.showMoney(name: name, money: money)
}
}
let salary = Salary()
let employee1 = Employees(name: "John", money: 45000, delegate: salary)
func generatereport() {
delegate.showMoney(name: name, money: money)
}
}
let salary = Salary()
var employee1 = Employees(name: "John", money: 45000, delegate: salary)
employee1.delegate = BasicSalary()
employee1.generatereport() // "Salary is over the minimum"

Errors are common in computer programming. Either our code or the code
provided by libraries and frameworks may return errors. No matter how
many precautions we take, we can't guarantee success and many problems
may be found as our code tries to serve its purpose. For this reason, Swift
introduces a systematic process to handle errors called Error Handling.
Throwing Errors

mystock.sold(amount: 8)
print("Lamps in stock: \(mystock.totalLamps)") // "Lamps in stock: -3"

The code in Listing 3-188 defines a structure called Stock that manages the
stock of lamps available in the store. The class includes the totalLamps
property to store the number of lamps we still have available and the sold()
method to process the lamps sold. The method updates the stock by
subtracting the number of lamps we have sold from the value of the
totalLamps property. If the number of lamps sold is less than the number of
lamps in stock, everything is fine, but when we sell more lamps than we
have, as in this example, there is clearly a problem.
To throw an error from a method, we must define the types of errors
available, add the throws keyword to the definition (between the arguments
and the returning data types), detect the error, and throw it with the throw
keyword.
Now that we have a method that can throw errors, we must handle the
errors when the method is executed. Swift includes the try keyword and
the do catch statements for this purpose. The do catch statements create two
blocks of code. If the statements inside the do block return an error, the
statements in the catch block are executed. To execute a method that
throws errors, we must call the method inside the do statement with the try
keyword in front of it.
do {
try mystock.sold(amount: 8)
} catch Errors.OutOfStock {
print("We do not have enough lamps")
}

This code expands the previous example to handle the error thrown by the
sold() method. Because of the addition of the try keyword, the system tries
to execute the sold() method in the mystock structure and check for errors. If
the method returns the OutOfStock error, the statements inside the catch
block are executed. This pattern allows us to respond every time there is an
error and report it to the user or correct the situation without having to
crash the app or produce unexpected results.
IMPORTANT: You can add as many errors as you need to the Errors
enumeration. The errors can be checked later with multiple catch
statements in sequence. Also, you may add all the statements you
need to the do block. The statements before try are always executed,
while the statements after try are only executed if no error is found.
If the error is not one of the types we are expecting, we can print
information about it. The information is stored in a constant called error
that we can read inside the catch block.
do {
try mystock.sold(amount: 8)
} catch {
print(error) // OutOfStock
}

On the other hand, if we do not care about the error, we can force the try
keyword to return an optional with the syntax try?. If the method throws an
error, the instruction returns nil, and therefore we can avoid the use of the
do catch statements.
The instruction at the end of Listing 3-192 returns the value nil if the
method throws an error, or an optional with the value returned by the
method if everything goes right.
Sometimes, we know beforehand that a throwing method is not going to
throw an error and therefore we want to avoid writing unnecessary code.
In cases like this, we can use the syntax try!. For instance, the following
code checks if there are enough lamps before calling the sold() method, so
we know that the instruction will never throw the OutOfStock error.
if mystock.totalLamps > 3 {
try! mystock.sold(amount: 3)
}
print("Lamps in stock: \(mystock.totalLamps)")

Results

Sometimes we need to return more than just an error. For this purpose,
the Swift Standard Library defines the Result enumeration. This
enumeration defines two cases with associated values to use in case of
success or failure called success() and failure(). The Result enumeration is
generic, which means that the data types of the associated values can be
anything we want. For instance, in the following examples we define a
Result enumeration of type <Int, Errors> to return an integer and the
OutOfStock error defined in the previous example.
The sold() method in Listing 3-194 now returns a Result value of type <Int,
Errors>, so if an error occurs, the method can return a failure() value with the
associated value OutOfStock, but if we have enough lamps to fulfill the order,
we can return a success() value with the remaining number of lamps. The
result can be processed by a switch statement. We check whether the value
returned by the method is failure() or success(), get the associated value with
a constant, and proceed accordingly. In this case, there are enough lamps
available, so a message is printed on the console with the remaining stock.
Instead of using a switch statement, we can use the following method
defined by the Result enumeration.
The only purpose of the get() method is to simplify the code. Now, instead
of a switch statement, we can use a do catch.
The result is the same, but now all we need to do is to call the get() method.
If the method doesn't return an error, the remaining stock is printed on the
console, otherwise, the catch block is performed, and an error is printed
instead.
CHAPTER 4 - INTRODUCTION TO
FRAMEWORKS
4.1 Frameworks

The programming tools introduced in previous chapters are not enough to
build professional applications. Creating an app requires accessing complex
technologies and performing repetitive tasks that involve hundreds or even
thousands of lines of code. Faced with this situation, developers always
implemented pre-programmed codes that perform common tasks. These
pieces of code are organized according to their purpose in what we know
as frameworks.
Frameworks are libraries (pre-programmed code) and APIs (Application
Programming Interfaces) that we can use to add functionality to our
applications. This includes managing databases, creating graphics on the
screen, storing files, accessing resources on the Web, sharing data online,
and more. These frameworks are essential for building professional
applications for Apple devices and are therefore part of the SDK (Software
Development Kit) included with Xcode.
If we already have a String value in our code, we can cast it into an NSString
object with the as operator.
A String structure can be turned into an NSString object with the as operator
because they are interconnected. It is said that the String structure bridges
with the NSString class. This means that we can access the functionality
offered by the NSString class from a String structure, including the following
properties and methods.
var age = 44
var mytext = String.localizedStringWithFormat("My age is %d", age)
print(mytext) // "My age is 44"

There are different placeholders available. The most frequently used are
%d for integers, %f for floating-point numbers, %g to remove redundant 0
(zeros), and %@ for objects and structures. We can use any of these
characters and as many times as necessary. This is like what we would get
with string interpolation, but with this method we can also format the
values. For instance, we can determine the number of digits a value will
have by adding the amount before the letter.
The code in Listing 4-5 formats two numbers, a double and an integer. The
double is processed with the %.2f placeholder, which means that the value
is going to be rounded to two decimals after the point, and the integer is
processed with the %.5d placeholder, which means that the number in the
string is going to contain a total of five digits.
Other NSString class methods perform operations that are already
available for String values, but they produce a more comprehensive result.
For example, the compare() method compares strings like the == operator
does, but the value returned is not just true or false.
The strings stored in the fruit and search variables in Listing 4-7 are different,
but because of the caseInsensitive option, they are considered equal. This
type of comparison is very common, which is why the class includes the
caseInsensitiveCompare()method that all it does is calling the compare() method
with the caseInsensitive option already set.
Despite this being the most common scenario, we can perform more
precise comparison by providing the range of characters we want to
compare.
This example compares only the initial characters of a string to check the
area code of a phone number. The code defines a range that goes from the
first character of the phone variable to the position before the - character.
This range is provided to the compare() method and in consequence the
value of the search variable is compared against the first three characters.
We can also use ranges to search for strings using the range() method. This
method searches for a string inside another string and returns a range that
determines where the string was found.
The range() method returns an optional value that contains the range where
the string was found or nil in case of failure. In Listing 4-9, we search for the
value of the search variable inside the text variable and check the optional
value returned. When we have a range to work with (which means that the
value was found) we use it to call the replaceSubrange() method of the String
structure to replace the characters in the range with the string "Red" (see
Listing 3-67). Notice that because search values are usually provided by the
user, we trim the value of the search variable with the trimmingCharacters()
method to make sure that there are no space characters at the beginning
or the end of the string (the two spaces after the word "black" are
removed).
Ranges

The NSRange class includes two properties to retrieve the values: location
and length. The following example initializes an NSRange object from a Swift
range and prints its values.
The following example shows how to create NSNumber objects and how to
get them back as Swift data types to perform operations.
Besides its own data type, Foundation also provides the means to format
numbers. Every time we print a number, all the digits are shown on the
screen, including all the decimal digits. In Listing 4-4, we explained how to
specify how many digits of a number we want to include in a string using
placeholders (e.g., %.2f), but this is not customizable enough. To provide a
better alternative, the framework includes the following formatting
method.
To format a number, we must call this method from the instance with the
styles we want to apply to it. The styles are defined by a structure that
conforms to the FormatStyle protocol. For numbers, the framework defines
the IntegerFormatStyle and the FloatingPointFormatStyle structures. These
structures include the following methods to style a number.
The styles are provided one by one with dot notation. We first get the
styling structure from the number property. (In this case, the number is a
Double so the value of the property is an instance of the
FloatingPointFormatStyle structure.) Next, we call the precision() method, and
send to this method the value returned by the fractionLength() method,
which formats the number with 2 decimal digits. As a result, we get a string
with the value "32.57" (the value is rounded up).
Styles can be concatenated, one after another, with dot notation. For
instance, in the previous example, the number was rounded up by default,
but we can change this behavior by applying the rounded(rule:) method, as
shown next.
We can also show the sign in front of the number (+ or -). In the following
example, we always show the sign except when the number is equal to 0.
Listing 4-16: Adding the sign

import Foundation
On the other hand, the currency(code:) method can produce a number with
any format and currency symbol we want. The currency is defined by the
string assigned to the argument. There are values for any currency
available. For instance, the USD string is for American Dollars, the CAD
string is for Canadian Dollars, EUR for Euros, and so on. The following
example gets the number expressed in Canadian dollars.
If the initializer requires an interval, as those in the code of Listing 4-19, the
value is specified in seconds. An easy way to calculate the seconds is
multiplying every component. For example, the date for the nextday object
created in our example is calculated adding 1 day to the current date. The
number of seconds in 1 day are calculated by multiplying the 24 hours of
the day by the 60 minutes in an hour by the 60 seconds in a minute (24 *
60 * 60). For the tendays object, we apply the same technique. This
initializer adds the interval to a specific date (nextday). The seconds are
calculated by multiplying the components, albeit this time it multiplies the
previous result by -10 to get a date 10 days before nextday. (We will see
better ways to add components to a date later.)
Besides the initializers, the class also includes type properties that return
special dates. Some of these properties produce values that are useful to
set limits and sort lists, as the following.
var days = 7
if today.compare(event) == .orderedAscending {
let interval = event.timeIntervalSince(today)
print("We have to wait \(interval) seconds")
}

The dates in Date structures are not associated to any calendar. This means
that to get the components in a date (year, month, day, etc.) we must
decide first in the context of which calendar the date is going to be
interpreted. The calendar for a date is defined by the Calendar structure.
This structure provides properties and methods to process a date
according to a specific calendar (Gregorian, Buddhist, Chinese, etc.). To
initialize a Calendar structure, we have the following initializer and type
property.
The Calendar structure works along with the DateComponents structure to read
and return components from a date. The instances created from the
DateComponents structure include the properties year, month, day, hour, minute,
second, and weekday to read and set the values of the components. The
following example combines these tools to get the year of the current
date.
In Listing 4-21, we get a reference to the calendar set in the system from
the current property and then use the dateComponents() method to get the
year from the current date.
Several components may be retrieved at once by adding the corresponding
properties to the set. The following example gets the year, month, and day
from the current date.
The date(from:) method of the Calendar structure returns a new date with the
values provided by the DateComponents structure. The components which
values are not explicitly defined take values by default (e.g., 12:00 AM).
Generating a new date requires a specific calendar. For example, in the
code of Listing 4-23, the values of the components are declared with the
format established by the Gregorian calendar. In this case, we rely on the
calendar returned by the system, but if we want to use the same calendar
no matter where the app is executed, we must set it ourselves from the
Calendar initializer.
let id = Calendar.Identifier.gregorian
let calendar = Calendar(identifier: id)
let id = Calendar.Identifier.gregorian
let calendar = Calendar(identifier: id)
var comp = DateComponents()
comp.day = 120
This example calculates the days between a birthdate and the current date.
The value returned by the date() method used to generate the birthdate
returns an optional, so we unwrap it before calculating the difference. We
assign this value to the olddate constant and then compare it with the
current date. The number of days between dates is returned and printed
on the console.
Another way to specify intervals between dates is with the DateInterval
structure. This structure allows us to create an interval with Date values.
The following are the initializers.
The DateInterval structure also offers the following properties and methods.
components.year = 2020
components.month = 8
components.day = 21
var future = calendar.date(from: components)
The code in Listing 4-27 creates two dates, birthday and future, and then
generates an interval from one date to another. The contains() method is
used next to check whether the current date is within the interval or not.
As with numbers, Foundation also provides the tools to format dates. The
Date structure defines two versions of the formatted() method for this
purpose.
The code in Listing 4-29 calls the weekday() method from the FormatStyle
structure returned by the dateTime property to get the day of the week. In
this case, we call the method with the value wide, which returns the day's
full name ("Friday"). Only one component is included in this example, but
we can add more by concatenating the methods with dot notation, as we
did before for numbers.
In this code, we implement the day(), month(), and hour() methods. The result
is a string with a date that includes the month (full name), the day, and the
hour ("June 18, 6 PM").
Notice that the order in which the methods are called doesn't matter. The
date and time are always formatted with a standard format that depends
on the user's locale (language and country). This is because the formatted()
method processes dates according to local conventions, including the
language, symbols, etc. This means that the components of a date are
interpreted according to the conventions currently set on the device. For
example, the same date will look like this "Tuesday, August 6, 2021" for a
user in the United States and like this "2021 8 6 年 月 日 星期二 " for a user in
China. How dates are processed is determined by an object of the Locale
structure. Every device has a Locale structure assigned by default, and our
code will work with it unless we determine otherwise. To get a reference to
the current structure or create a new one, the Locale structure includes the
following initializer and type property.
This example creates a new Locale structure with the zh_CN identifier,
which corresponds to China and the Chinese language, and then formats
the date with this locale and the day(), month(), and year() methods. The result
is a string with the date in Chinese.
dateTimeStyle.timeZone = madridTimeZone
let madridTime = mydate.formatted(dateTimeStyle.hour().minute().second())
The code in Listing 4-32 creates two TimeZone structures, one for Tokyo's
time zone and another for Madrid's. If successful, we initialize a FormatStyle
structure and format the date twice, first for Tokyo and then for Madrid.
Notice that the TimeZone structure is assigned to the timeZone property of the
FormatStyle structure before using it to format each date.
The code in Listing 4-35 adds two lengths of different units (meters and
kilometers). The system converts kilometers to meters and then performs
the addition, returning a Measurement structure with a value in meters (the
basic unit).
If we want everything to be performed in the same unit, we can convert a
value to a different unit using the convert() or converted() methods. In the
following example, we convert the unit of the length variable to kilometers
and perform the addition again in kilometers.
The values of a Measurement structure are printed as they are stored and
with the units they represent, but this is usually not what we need to show
to users. To prepare the value for display, the Measurement structure defines
the formatted() method.
By default, the formatter rounds the number. That's why in this example
the result of converting 40 kilometers to miles is 25, when it should've
been 24.8548. If we want to specify a different format, we can add the
numberFormatStyle argument and implement the methods provided by
the IntegerFormatStyle and FloatingPointFormatStyle structures introduced before.
For instance, we can specify a precision of 2 digits for the decimal part with
the fractionLength() method, as shown next.
The code in Listing 4-40 initializes a FormatStyle structure with the locale
configured for China and then calls the formatted() method with this
structure to format the value. Notice that the FormatStyle structure is
defined inside the Measurement structure, which is a generic structure and
therefore we must specify the type of values the structure is going to
process. In this case, we are working with units of length so we must
specify the UnitLength data type.
Timer

Timers are objects that perform an action after a specific period of time.
There are two types of timers: repeating and non-repeating. Repeating
timers perform the action and then reschedule themselves to do it again in
an infinite loop. Non-repeating timers, on the other hand, perform the
action one time and then invalidate themselves. Foundation defines the
Timer class for this purpose. The class includes the following properties and
methods to create and manage timers.
print("Wait 5 seconds...")
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { (timer) in
print("The time is up")
}

var counter = 0
func startTimer() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timerref) in
report(timer: timerref)
}
}
func report(timer: Timer) {
print("\(counter) times")
counter += 1
if counter > 10 {
print("Finished")
timer.invalidate()
}
}
startTimer()

With the methods provided by Foundation and the String data type, we can
modify a string, remove or insert a string from another one, and determine
whether a string contains another string, or the characters match a specific
pattern. For instance, we can check if a string contains an email address.
For simple patterns, like finding a word at the beginning or the end of the
string, these tools are more than enough, but determining if a string
represents a phone number, an email address, or extracting the values of
more complex structures like the content of a spreadsheet, requires
looping through the characters one by one and comparing them with
previous characters to recognize valid or invalid patterns. This process is
generally cumbersome and error-prone. To simplify our work, Swift
implements regular expressions.
Regular Expressions

[] match any character in the square brackets (e.g., [abc] matches the
character a or b or c). It can also match a character in a range of
consecutive characters. The range is defined with a hyphen, as in [a-z]
to match a lowercase letter from a to z, or [a-zA-Z] to match a lowercase
or uppercase letter from a to z.
[^] match any character different from those in the square brackets
(e.g., [^abc] matches any character that is not a or b or c).
. (dot) matches any character except a new line (new lines are
represented by the special character \n).
| (or) matches the character on the left or the right (e.g., a|b matches
the character a or the character b).
These special characters match a single character. To specify quantity,
regular expressions include the following.
There are also special characters to delineate the expression. The following
are the most frequently used.
The values returned in the Match structure depend on the expression. In the
previous example, the output property returns a single string with the
sequence of characters that match the regular expression, but we can
define subexpressions to capture specific values. For instance, if we want
to get back the name, we can define a subexpression with parentheses.
In this example, the output property returns a tuple. The first element of the
tuple is the string that matches the regular expression, as before, but the
second value is the string that matches the expression between
parenthesis. (In this case, one or more letters after a space.)
The firstMatch() method finds the first sequence of characters that match the
regular expression and returns that value. If we want to get all the matches
in a string, we must implement the matches() method.
The split() method is useful when we have to process long texts or a text file
line by line. In the following example, we use triple quotes to tell the
compiler to add the characters \n after each line of text to generate a new
line (see Listing 2-24). This is how usually text files are structured. To read
each line, we define a regular expression with the special character \n and
then call the split() method to get each line of text in an array.
The framework also includes structures to capture values from the string.
The Regex Builder is built from a Regex structure. The structure includes an
initializer that takes strings, structures, and regular expressions in literal
notation to define the final expression. For instance, the following Regex
Builder defines the same regular expression used before to determine if a
string contains a name.
import RegexBuilder
The string we are processing includes numbers at the beginning and the
end. The first number represents the units to be added to the stock, and
the last number represents the current stock. To get the first number as an
integer, we capture one or more digits. (The digit property returns a
CharacterClass structure that represents numbers between 0 and 9.)
Capturing the last number is a bit tricky. Because we may have any
characters in between, to match the text we use the any property, which
returns a CharacterClass structure that represents any character. But this
includes numbers, which means that the number at the end will also be
matched by this pattern and the last Capture structure won't have anything
else to match. By default, the behavior of the OneOrMore structure is
determined by the RegexRepetitionBehavior structure returned by the eager
property. This structure tells the system to match as many characters as it
can. But we can change this behavior by assigning the reluctant property
instead, as we did in our example. The structure returned by this property
asks the system to match the current expression until any character
matches the next expression, so when the number is found, the system
moves to the next OneOrMore structure that matches digits and we are able
to capture our second number.
Although we can transform any value we want, some like dates and
complex numbers may require long patterns. To simplify our work, the
Foundation framework includes properties and methods we can use to
match these values. The following are the most frequently used.
The following example shows how to match dates and currency. In this
case, we process a string with multiple items and split the content in lines
of text to process each value, as we did in Listing 4-46.
The first pattern matches one occurrence of a date using the numeric
format and the current locale and time zone. After that, we match a string
of lowercase and uppercase letters, and finally match one occurrence of a
currency value in US Dollars. The three values are captured by Capture
structures and printed on the console. Notice that because we get a Date
value in return we are able to format the date using the formatted() method
introduced before.
What modern applications require the most from this old framework are
its data types. In Swift, Core Graphics’ data types are implemented as
structures, with their own initializers, properties, and methods. They can
store values that represent attributes of elements on the screen, such as
position or size. For instance, the following is the structure used to specify
coordinate values.
There is also a more complex structure called CGRect that we can use to
define and work with rectangles. This data type includes the following
initializers and properties.
The CGSize and CGPoint structures may be initialized with their member
initializers, but the CGRect structure provides an additional initializer to
create the instance from the values of its internal structures.
The origin and size properties of a CGRect value are CGPoint and CGSize
structures, respectively, so they can be copied into other variables or
properties as any other values.
When we don't have initial values for the coordinates or the size, we can
use the zero type property to create a structure with all the values
initialized to 0.
The myrect variable in Listing 4-55 is a CGRect structure with all its properties
initialized with the value 0. Assigning the value of the zero property to a
variable is the same as using the initializer CGRect(x: 0, y: 0, width: 0, height: 0).
The CGRect structure also includes properties to calculate values from the
coordinates and size. For example, the midX and midY properties return the
coordinates at the center.
Applications are built from several files and resources, including our own
codes, frameworks, images, databases, and more. Xcode organizes this
information in projects. An Xcode project includes all the information and
resources necessary to create an application. The welcome window,
illustrated in Figure 1-2, presents a button called Create a new Xcode
project to initiate a project. When this button is clicked, a window appears
to select the type of project we want to create.
The Product Name is the name of the project. By default, this is the name
of our app, so we should write a name that is appropriate for our
application (this value can be modified later). Next is the Team's account.
This is the developer account created with our Apple ID or our company's
account. If we haven't yet registered our account with Xcode, we will see
the Add Account button to add it. The next value is the Organization
Identifier. Xcode uses this value to create a unique identifier for our app
and therefore it is recommended to declare it with an inverted domain, as
we did in our example (com.formasterminds). Using the inverted domain
ensures that only our app will have that identifier. And finally, there is a list
of options we can select to incorporate additional functionality to our
project, such as Core Data and Tests. We will explore these alternatives in
further chapters, but should be left unchecked if we are not planning to
use them.
As mentioned above, the Team's field shows a list of developer accounts
registered with Xcode. If we haven't yet inserted our account, we will see
the Add Account button instead. Pressing this button opens Xcode
preferences and a window to insert our Apple ID. (This is the same ID used
to initialize our computer, but we can create a new one if necessary.)
Once we insert our Apple ID, the Apple account is configured to work with
our copy of Xcode and we can select it from the list. With the account
selected and all the information inserted in the form, we can now press the
Next button to select the folder where our project is going to be stored.
Xcode creates a folder for each project, so we only have to designate the
destination folder where we are going to store all our projects and
everything else is generated for us.
IMPORTANT: Although it’s not mandatory, you should get your own
domain and website. Apple not only recommends the use of an
inverted domain to generate the Bundle Identifier, but at the time of
submitting your app to the App Store you will be asked to provide
the web page used for promotion and where the users should go for
support (see Chapter 20).
Once the project is created, Xcode generates some files according to the
selected template and presents the main interface on the screen. Figure 5-
4 shows what this interface looks like.
The Editor Area is the only non-removable area in the interface. By default,
this area always displays the content of the selected file, but we can
modify the layout from the buttons in the upper right corner (Figure 5-4,
number 8). The button on the right adds more editor panels to the area
(Figure 5-5, number 2). New editors are placed on the right side of
previous editors, but we can place them below by pressing and holding the
Option key. Figure 5-5, below, shows what we see when we press the Add
Editor button. Two editors are shown side by side.
These inner panels are useful when we need to edit two or more files at
the same time. We can divide each editor as many times as we want, resize
them by dragging the lines between them, and close them by pressing the
X button in the upper left corner.
On the other hand, the button on the left (Figure 5-5, number 1) shows a
popup menu with options to expand the editor, as shown below.
In SwiftUI, the most useful options are the Canvas, the Layout, and the
Minimap. The Canvas option displays a panel where we can see a preview
of the views as they are created, the Layout option allows us to place the
preview on the right or at the bottom of the code, and the Minimap option
shows a visual representation of our code that we can use for reference
and navigation.
In addition to the previews offered by the Canvas, there are two other
ways to run and test our application from Xcode: the simulator and a real
device. The buttons to select these options, run and stop the application,
are located on the toolbar (Figure 5-4, number 1 and number 2).
Applications always run on a specific destination and can have different
configurations called Schemes. The destination could be multiple things,
from real devices to windows or simulators, and the scheme defines things
like the region where the device is located, or the human language used by
the app to display the information on the screen. To set the scheme and
destination, Xcode's toolbar includes two buttons (Figure 5-4, number 2). If
we click on the button on the left (the one with our app's name), a menu
appears with options to edit the current scheme, create a new one, or
select the one we want to use.
On the other hand, the button on the right allows us to select the
destination. The Multiplatform template includes a target configured to
create an application for iPhones, iPads, and Mac computers. Therefore,
when we want to run our application, all we need to do is to click this
button to open the drop-down list (Figure 5-4, number 2) and select a
simulator or a device, as shown below.
After the destination is selected, we can press the Play button to run the
application (Figure 5-4, number 1). If we use a simulator, a new window
opens where we can see our app and interact with it, as shown below.
The simulator works out of the box, but to run the application on a device,
we must connect the device to the computer with a USB cable, select it
from the list, and then open the Settings app on the device, select the
Privacy & Security option, open the Developer Mode option at the bottom,
and turn Developer Mode on, as shown below.
The app's configuration is stored on a target. The target includes things like
the app's name and version, the systems it will support, and the
capabilities the app will have, such as access to the camera or iCloud
servers. The option to change these values is available in the Navigator
Area (Figure 5-4, number 6). Once we click on it, a series of panels are
displayed in the Editor Area (Figure 5-12, number 1).

From the General panel we can change basic aspects of the application
such as the devices the app will support, the app's name, version, available
orientations for iPhones and iPads, and the deployment target (the
operating systems the app supports). In the Signing & Capabilities panel we
can set up the signing certificates required for distribution (usually set up
automatically by Xcode) or assign capabilities to the app, such as access to
the camera or iCloud servers. Another useful panel is called Info. Here we
can find a list of configuration values and insert new ones.
The information in this panel is stored in a plist file; which is a file with a
format that allows us to store keys and values. The keys represent
configuration options and the values are what we want to assign to our
app. These configuration options include those declared from other panels,
like the available orientations set in the General panel, and also custom
options, like the image we want to show when the app is launched, as we
will see later.
SwiftUI Files

The Multiplatform App template includes two Swift files: one with the code
to initialize the app (TestApp.swift in our example) and another to define
the user interface (ContentView.swift). Figure 5-14, below, shows the
Navigator Area with all the items created by the template.
The first file is named after the application and includes the App suffix to
indicate that the code it contains is in charge of initializing the app
(TestApp in our example). The following is the code generated by the
template for this file.
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

Due to its capacity to create applications for every device, the WindowGroup
structure is the one implemented in the template of a Multiplatform
project, as illustrated in Listing 5-1. Notice that the argument is declared
with a trailing closure and because there is only one statement in the
block, we didn't have to include the return keyword. This is common
practice in SwiftUI to simplify the code and make it easy to read.
The WindowGroup structure creates a Scene with a window. Windows
determine the space where the graphics are displayed, but they do not
generate any visible content. The user’s interface is built inside a window
from similar containers called Views. These views are rectangular areas of
custom size, designed to display graphics on the screen. Some views are
used as containers while others present graphic tools, such as buttons and
switches, and graphic content, such as images and text. The views are
organized in a hierarchy, one within the other.

SwiftUI views are defined by structures. In the example provided by the
App template, the closure assigned to the WindowGroup structure initializes
and returns a structure called ContentView. This is a custom structure
included by the template to define the initial view. (The initial view
represents the initial content the user sees on the screen when the app is
launched.) The structure is defined in the ContentView.swift file, as shown
next.

Canvas

SwiftUI files include the definition of two structures, one to declare the
view and another to create the preview on the canvas. This second
structure conforms to the PreviewProvider protocol which requires the
implementation of a type property called previews that returns the view or
group of views to display on the canvas. The following is the preview
structure defined by the App template for the ContentView view.
The preview always matches the destination device selected from the
toolbar (see Figure 5-9). Once we set the device, the canvas shows options
at the bottom to configure the preview.
The code in Listing 5-4 defines a function called reverseit() that receives
an array of integers and returns a ReversedCollection value with the values in
reverse order, and then casts the collection returned by the function into
an array and print it on the console.
This code runs fine and there are no issues with it, until we decide to
work with different value types, such as strings. Instead of
ReversedCollection<Array<Int>>, the return data type would have to be defined
as ReversedCollection<Array<String>>. Although in this case it may seem easy to
switch types, the generic data types used to create SwiftUI views can be
complex and replacing them repeatedly in generic properties and methods
can be time consuming and error prone. But if we know that the values we
want to return conform to the same protocol, we can declare the return
type as opaque and let the compiler figure out the data type for us. When
the compiler finds an opaque type, it takes care of determining the real
data type of the value and process it as such.
Opaque types are declared with the some keyword followed by the
name of the protocol to which the type conforms. For instance, collections
such as the one returned by the reversed() method, conform to a protocol
called Collection, so we can define the return type as some Collection and let the
compiler figure out the real data type.
This example defines the same reverseit() function as before, but this
time the function receives an array of strings and returns the opaque type
some Collection. The value returned by the function is of type some Collection,
but the compiler detects the value's data type as
ReversedCollection<Array<String>> and process it as such, which means that we
are still able to turn it into an Array and print it on the console.
The Text structure takes a string and returns a view that displays the text on
the screen. The structure defines multiple initializers. The following are the
most frequently used.
Because of text being the primary means of communication, Text views are
implemented all the time in SwiftUI applications, and that is why the
ContentView view generated by the App template includes one. The Text view
included in the template is created with a String value to display the text
"Hello World!" on the screen (see Listing 5-2), but a Text view can also
include values with string interpolation.
Of course, we can include and format other types of values. For instance,
we can use the formatted() method on a Date value to show a date.
The Text structure defines a specific initializer to show dates. The initializer
includes an argument that takes a DateStyle structure to format the date.
The formats available are not as comprehensive as those provided by the
formatted() method, but the advantage of using this formatter is that some of
the styles available can update the values as they change. For instance, one
of the type properties included by the DateStyle structure is called timer. This
formatter displays the date as a counter that starts counting from the
current date.
Views are presented with attributes by default, such as a standard font and
color, but the View protocol defines methods to modify their aspect. These
methods are called Modifiers, and they are executed right after the
instance is created, as in the following example.
When the system reads the body property of the example in Listing 5-10 to
build the interface, it creates an instance of the Text structure with the
string "Hello World", and then calls the font method on this structure. This
method creates a new view with the largeTitle attribute applied to the text
and returns that view. As a result, the body property produces a view with a
large title. If we open the canvas, we can see the change in real time.
Additionally, Xcode offers a library with a list of views and modifiers that
we can incorporate to our interface by dragging and dropping them onto
the canvas or the code. The library is activated from the Library button in
the toolbar (Figure 5-4, number 5), and the lists of views and modifiers are
selected from the buttons at the top of the window. The first button
presents a list with all the views we can add to the interface and the
second button opens a list with all the modifiers available. We can also use
the search bar at the top of the window for a quick search. Figure 5-24,
below, illustrates how to add a modifier from this library to change the
color of the text.

Do It Yourself: Click on the ContentView.swift file and open the
canvas. Press the Selectable button to activate the Selectable mode
(Figure 5-22, number 1). Move the mouse to the "Hello World" text
on the canvas, press and hold the Command key and click on it. You
should see a context menu (Figure 5-23, left). Select the Show
SwiftUI Inspector option. In the next window, click on the Text field,
change the text to "Goodbye World", and press Enter. You should see
the text changing on the canvas and the code. Experiment with
different values and try to add modifiers to the view from the
Library, as shown in Figure 5-24.
Most modifiers are defined by the View protocol and then implemented by
the structures that conform to it. There are common modifiers that apply
to most views and others that are more specific. The following are some of
the modifiers used to determine the size of the view.
Figure 5-25: View with a fixed size and the content aligned to the left
(leading)
The arguments in the frame() modifier are optional. We can declare only the
width or the height, and the rest of the arguments will be defined with the
values set by the view. This is particularly useful when we want to turn one
side of the view flexible. Flexible views are defined with the maxWidth and
maxHeight arguments. For instance, we can apply the maxWidth
argument to extend the view to the left and right side of the window, and
let the content determine the height.
Listing 5-12: Creating flexible containers

struct ContentView: View {
var body: some View {
Text("Hello World!")
.frame(minWidth: 0, maxWidth: .infinity)
}
}

The value infinity is a type property defined in the CGFloat structure that asks
the system to expand the view to occupy all the space available in its
container. In our example, we applied this value to the maxWidth
argument, so the Text view extends to the edges of the screen.
Another way to specify a custom size is with the padding() modifier. The
padding is inserted between the content and the edges of the view. There
are different ways to determine the padding's thickness. For instance, if we
don't specify any value, the system assigns one by default, depending on
the device, but we can also provide a CGFloat value to declare a specific
thickness in points.
Listing 5-13: Adding a padding to the view

struct ContentView: View {
var body: some View {
Text("Hello World")
.padding(25)
}
}

The code in Listing 5-13 assigns a padding of 25 points to the Text view. The
padding is applied between the text and the view's frame, as shown below.
The View protocol defines a structure called EdgeInsets that we can use to
specify a width for each side of the view.
The code in Listing 5-14 assigns padding to specific sides of the view.
Another way to achieve the same is with the values of an enumeration
provided by SwiftUI called Edge. This enumeration includes the values
bottom, leading, top, and trailing to represent each side. We must declare a set
with the values that represent the sides we want to modify and a second
argument with the thickness we want to assign to the padding. The
following example adds a padding of 50 points at the view's top and
bottom.

In addition to the view, we can also style its content. For instance, the View
protocol defines modifiers that are especially useful with Text views. The
following are the most frequently used.
The most important aspect of a text is the font. The system defines
standard fonts and sizes to show the text produced by a Text view, but we
can specify our own with the font() modifier. The font() modifier assigns the
font to the view, but the font is defined by an instance of a structure
included in the SwiftUI framework called Font. With this structure, we can
create custom and dynamic fonts. Custom fonts are fonts provided by the
system or the developer, and dynamic fonts are the fonts Apple
recommends using because they adapt to the font size selected by the user
from Settings.
The easiest to implement are dynamic fonts. They are defined by type
properties provided by the Font structure, so all we have to do is to apply
the font() modifier with the property that represents the font type we want
to assign to the text. We have done this before with the largeTitle property
(see Listing 5-10), but the structure also includes the properties title,
headline, subheadline, body, callout, caption, and footnote.
With the system() method, we can get the standard font provided by the
system but of any size we want. The font and size defined by this method
are not affected by the choices the user makes from Settings.
Listing 5-17: Using the system font

struct ContentView: View {
var body: some View {
Text("Hello World")
.font(Font.system(size: 50))
}
}

This example displays a text with a size of 50 points that always remains at
that size no matter the changes performed by the user, but the font type is
the one defined by the system. If we want to use a custom font, we have to
implement the custom() method.
Operating systems come with a set of standard fonts. If the font we want to
use is already included in the system, we just need to specify its name and
size, as in the following example.
If the font we want to include is not provided by the system, we must copy
the file into the project. Including the file in our project is easy; we must
drag it from Finder to the Navigator Area, as shown below.
Figure 5-32: Dragging files from Finder to our Xcode’s project
When we drop the files into our Xcode project, a window asks for
information about the destination and the target. If we want the files to be
copied to the project’s folder (recommended), we must activate the option
Copy items if needed, as shown in Figure 5-33 below. We must also indicate
that the files are going to be added to the target created for our project
from the Add to targets option.
After these options are selected and the Finish button is pressed, Xcode
includes the files with the rest of the files in our project. But to be able to
use our custom font, we must modify the app's configuration from the Info
panel (see Figure 5-13). Every option on the list includes a + button. By
pressing any of these buttons, we can add a new key (Figure 5-34, number
1). The key we need in this case is called "Fonts provided by application".
This key already includes an item (Item 0), so all we need to do to add a
font is to press the arrow on the left to expose the item (Figure 5-34,
number 2) and then replace its value with the name of the file that
contains our font (Figure 5-34, number 3).

Do It Yourself: Download the horsepower.ttf file from our website or
provide your own. Drag the file from Finder to the project's
Navigator Area (Figure 5-32). Make sure to check the option "Copy
items if needed" and the target (Figure 5-33). Go to the app's
settings (Figure 5-4, number 6), open the Info panel, click on the +
button in any of the rows, and select the "Fonts provided by the
application" key. Click on the arrow at the option's left hand side to
reveal the items. You should see the Item 0 with no value. Click on
the value field to change it. Copy and paste the name of your font's
file, including the extension. Modify the ContentView view with the
code in Listing 5-19. Press the Resume button to see the text with
the new font on the canvas.
Applying the rest of the modifiers available for Text views is straightforward,
as shown next.
The structure in Listing 5-20 defines a Text view with a largeTitle font, a
weight of type heavy, a shadow, and underlines the text. In this example, we
define the radius, x, and y arguments of the shadow() modifier to 1 to cast a
subtle shadow that extends to the right and bottom of the text.
Textviews can be provided as the content of other Text views, which allows
us to assign different styles to each portion of the text, as shown below.
In this example, we applied the largeTitle and underline modifiers, but because
the underline modifier was applied to the second Text view, only the text in
this view is underlined.

The Font structure also includes the following modifiers to style the font.
Because these modifiers are defined by the Font structure, they are applied
to the font, not the view. For instance, we can apply the weight() modifier to
the Font structure returned by the largeTitle property to get a bold text.
By default, Text views can show multiple lines of text, but we can
implement modifiers provided by the View protocol to set a limit on the
number of lines allowed or to format the text.
lineLimit(Int)—This modifier determines how many lines the text
can contain. The argument is an optional that indicates the number of
lines we want. By default, the value is set to nil, which means the view
will extend to include the number of lines necessary to show the full
text.
multilineTextAlignment(TextAlignment)—This modifier defines
the alignment of multiline text. The argument is an enumeration with
the values center, leading (default), and trailing.
lineSpacing(CGFloat)—This modifier determines the space
between lines.
truncationMode(TruncationMode)—This modifier determines
how the text is truncated when it doesn't fit inside the view's frame.
The argument is an enumeration with the values head, middle, and tail
(default).
textSelection(TextSelectability)—This modifier determines if the
text is selectable by the user (the user can copy the text and paste it
somewhere else). The argument is a structure with the type
properties enabled and disabled.
privacySensitive()—This modifier indicates that the view contains
sensitive information. It is used to prevent the system from exposing
private data.
The following example displays a text aligned to the center and with a
space of 5 points between lines.
If we limit the number of lines, we must consider how the text is going to
be displayed to the user when it is too long or does not fit within the view.
By default, the system truncates the text and adds ellipsis at the end to
indicate that part of the text is missing, but we can move the ellipsis to the
beginning or the middle with the truncationMode() modifier.
Some modifiers can change the colors of the view and the content. Colors
in SwiftUI are defined by a Color view. The following are some of the
structure's initializers.
The code in Listing 5-25 creates an orange Color view with a size of 250 by
100 points. In this case, the Color initializer was implemented with values
from 0.0 to 1.0 to determine the levels of red, green and blue, but RGB
colors (Red, Green, Blue) are usually determined with integer values from 0
to 255. If we want to work with these values, we can divide the number by
255. For instance, the following initializer assigns an RGB color with the
values 100, 228, 255 (cyan).
Colors defined with the initializers are static colors, which means they are
always the same, independent of the interface appearance (light or dark),
but we can assign dynamic colors with the structure's properties. Dynamic
colors adapt to the appearance. The following example creates a Color view
with the red property. In dark mode this color will look slightly different
than in light mode.
In addition to the SwiftUI files, the App template includes a file called
Assets (see Figure 5-14). This is a tool called Asset Catalog that makes it
easy to access resources, including images, icons, colors, and more. When
selected, Xcode shows a visual interface in the Editor Area to manage and
configure the content. The interface includes two columns: the column on
the left presents a list of sets of resources, such as images and colors, and
the column on the right displays the content of the selected set, as shown
below.
The type properties provided by the Color structure, like the red property
implemented in Listing 5-27, define colors that adapt to the appearance
(light or dark). With the Asset Catalog, we can define our own sets of
adaptive colors. The set is added from the Editor menu (Add New Asset /
Color Set), or by pressing the + button at the lower left corner (circled in
Figure 5-42). Once we select the Color Set option, Xcode creates a set with
two placeholders for the colors, one for the color to show in Any
appearance, and another for the Dark mode.

The name of the set is the name we use to reference the color from code.
In this example, we call it MyColor. To assign the color, we must select the
placeholder we want to change and define the color from the Attributes
Inspector panel. For instance, in the following example we change the
color for the dark appearance to orange.
In this example, the Color view will be white in light appearance, but orange
when the appearance is changed to dark.
The Asset Catalog includes two predefined sets called AccentColor and
AppIcon. The AppIcon set defines the icons we must provide to represent
the application. (Icons are the little images the user taps or clicks to launch
the app.) On the other hand, the AccentColor set defines the color used by
some views, such as buttons and other controls, to style their content. The
color by default is blue, but we can modify this set to define a new one. In
the example below, we change the accent color to green. From now on, all
the controls that use the accent color will be green.
Using these modifiers, we can apply colors to different parts of the view.
For instance, the foregroundColor() modifier assigns a color to the view's
content. In the following example, we use it to change the color of the text
in a Text view.
Besides changing the color of the text, we can assign a color to the view's
background. The background() modifier can take any view, but it is usually
applied with a Color view.
Listing 5-31: Assigning a background color to the view and the padding

struct ContentView: View {
var body: some View {
Text("Hello World")
.font(.largeTitle)
.padding(20)
.background(Color.gray)
}
}

Notice that the padding was applied before the background. This is
important because the order of the modifiers matters. Every time a
modifier is executed, a new view is created with the characteristics of the
previous view plus the changes requested by the modifier. For example,
the code in Listing 5-31 creates a Text view with the text "Hello World",
then the font() modifier creates a new view with a larger font, after that the
modifier creates another view with the characteristics of the
padding()
previous one but with a padding of 20 points, which expands the view's
frame 20 points on each side, and finally the background() modifier creates
another view with a background that covers the whole area occupied by
the previous view, which includes the padding. If we had declared the
background before the padding, the background color would have been
applied to the view generated by the font() modifier, which didn't include
the padding, as illustrated below.
In addition to the background, we can assign a border to the view with the
border() modifier. This modifier takes a view that represents the style of the
border (usually a Color view) and the width, and adds a border with those
characteristics.
If we have a view with a background or content that fills the view's frame,
such as a Color view or an image, we can round its corners with the
cornerRadius() modifier.
The overlay() modifier works like the background() modifier but instead of
displaying the view in the background, it does so on the front. For instance,
the following code adds a translucent yellow view in front of the view
generated by the previous example.
The Color view also includes the following property and method to apply
simple effects to the views.
The gradient property returns a gentle gradient generated from the original
color. It can be applied anywhere a Color view is implemented. For instance,
in the following example, we apply it to the background of our Text view.

Materials

Because we only have one view and the root view is white, the effect is
barely visible, but materials become useful when working with multiple
views and images, as we will see later.
Materials can also be applied to the view's content with the foregroundStyle()
modifier, as in the following example.

Images


One solution to this problem is to scale up a small image in devices with
higher resolution or scale down a big image in devices with lower
resolution. For example, we can expand an image of 300 x 400 pixels to
600 x 800 pixels and make it look like the same size in a screen with a scale
of 2x (a space of 300 x 400 points represents 600 x 800 pixels at this scale),
or we could start with an image of 600 x 800 pixels and reduce it to 300 x
400 pixels for devices with half the scale. One way or another, we have a
problem. If we expand a small image to fill the screen, it loses quality, and
if we reduce it, it occupies unnecessary space in memory because the
image is never shown in its original resolution. Fortunately, there is a more
efficient solution. It requires us to include in our project three versions of
the same image, one for every scale. Considering the image of our
example, we will need one picture of the husky in a size of 300 x 400 pixels
for devices with a scale of 1x, another of 600 x 800 pixels for devices with a
scale of 2x, and a third one of 900 x 1200 for devices with a scale of 3x.
Now, the images can be shown in the same size and with the same quality
no matter the device or the scale.
Providing the same image in different resolutions solves the problem but
introduces some complications. We must create three versions of the same
image and then select which one is going to be shown depending on the
scale of the device. To help us select the right image, Apple systems detect
the scale that corresponds to the image by reading a suffix on the file’s
name. What we need to do is to provide three files with the same name
but with suffixes that determine the scale for which they were designed.
The file containing the image for the 1x scale (300 x 400 pixels in our
example) only requires the name and the extension (e.g., husky.png), the
name of the file with the image for the 2x scale (600 x 800) must include
the suffix @2x (e.g., [email protected]), and the name of the file with the
image for the 3x scale (900 x 1200) must include the suffix @3x (e.g.,
[email protected]). Every time the interface requires an image, the system
reads the suffixes and loads the one corresponding to the scale of the
screen.
There are two ways to incorporate images into our project. We can drag
the files to the Navigator Area, as we did before for the font type (see
Figure 5-32), or we can add the images to the Asset Catalog. The latter is
the preferred option because it simplifies the management of a large
number of images. The images are added to the Asset Catalog and then
referenced from code by name. This is similar to what we have done to
create custom colors (see Figure 5-43). We create an Image Set and then
fill the placeholders with the images we want to add to the project.
New sets are added from the + button in the lower left corner (circled in
Figure 5-42) or the Editor menu at the top of the screen. If we open the
Editor menu and click on the option Add New Asset / Image Set, a new
empty set is created.
The name of the set is the name we are going to use to get the image from
code. Xcode calls the new set Image but we can click on it and rename it,
as we did for colors. Once the set is created, we can drag the files from
Finder to the corresponding squares. For example, the file husky.png
mentioned before goes inside the 1x square, the file [email protected] goes
inside the 2x square, and the file [email protected] goes inside the 3x square.
Figure 5-59, below, shows the Editor Area after the images of a husky are
dragged from Finder to the Asset Catalog and the name of the set is
changed to "husky".
Although we can add all of our images one by one, as we did in this
example, the process soon becomes tedious. An easy way to create a new
set is to drag the three images for the set to the Asset Catalog. Xcode
creates a new set with the images and assigns their names as the name of
the set. The creation and configuration of the set is done automatically
when we drag the files and drop them inside the Asset Catalog. In fact, we
can drag several files at the same time and Xcode takes care of extracting
the information and creating all the sets for us.
In addition to the image for every scale, we can also add versions for
different devices and appearances. By default, the set of images is assigned
to a Universal device, which means that the images of the set are going to
be display on every device, but we can add to the set images for a specific
device by selecting the options in the Attributes Inspector panel.
When a set is selected, the Attributes Inspector panel shows the list of
properties assigned to the set, including Devices and Appearance. If we
check the box of a device or select a different value for the appearance
(Dark or Light), the interface adds placeholders where we can drag and
drop the images for that specific attribute. For instance, in Figure 5-60, we
checked the box for iPhones and now we have three placeholders to add
images for that specific device. After the images are incorporated into the
Asset Catalog, we can load the image from our code using its name and the
system will pick the right version according to the characteristics of the
device where the app is running.
An Image view can load and display any image that was incorporated into
our project or added to the Asset Catalog. All we need is to specify its
name. For instance, the following example creates an Image view with an
image we put in the Asset Catalog called Toronto.
By default, Image views are the size of their content. If the image is larger
than the window, as in this case, the view will extend beyond the limits of
the screen.
The image is independent of the view. If we resize the view with the frame()
modifier, the image remains in its original size. To adapt the image to the
space provided by the view, we must apply the following modifiers.
There are several transformations we can apply to the image with these
modifiers. One alternative is to clip the image to the view's frame with the
clipped() modifier.
The clipped() modifier creates a new view that only shows the part of the
image that is within the view's frame.

This reduces the size of the visible image, but the image is still presented in
its original size, independent of the size of the view. To adapt the size of
the image to the size of the view, we have to make the image flexible with
the resizable() modifier.
The resizable() modifier creates a view that adjusts the size of the image to fit
the space available within the view's frame.
In this example, we create an Image view with the same image as before,
but this time we resize it with the resizable() modifier and make it fit within
the space available preserving its original aspect ratio with the aspectRatio()
modifier. The same effect can be achieved with the scaledToFit() modifier.
Listing 5-42: Resizing the image to fit within the view with the scaledToFit()
modifier

struct ContentView: View {
var body: some View {
Image("Toronto")
.resizable()
.scaledToFit()
.frame(width: 250, height: 100)
}
}

There are two modes available: fit and fill (scaledToFit() and scaledToFill())
Figure 5-64, below, shows what happens when we apply these content
modes to the image of our example.
In fit mode, the image is resized to fit within the view, even when that
leaves some parts of the frame empty (Figure 5-64, left). In fill mode, the
image is resized to fill the view, even when parts of the image may lie
outside the view (Figure 5-64, right). If we want to use the fill mode to fill
the view but don't want the image to go beyond the view's boundaries, we
can clip it with the clipped() modifier.
Listing 5-43: Resizing and clipping the image to fill the view

struct ContentView: View {
var body: some View {
Image("Toronto")
.resizable()
.scaledToFill()
.frame(width: 250, height: 100)
.clipped()
}
}

Often, the user interface must include a view with an image that adapts to
the space available. An easy way to achieve this is to make the image
resizable and set its mode to fit.
When the size of the frame is not declared, the view works along with the
content to set its final size. At first, the view takes all the space available in
its container, but then it asks the image what size to take. Because the
image mode was set to fit, the image allows the view to extend as much as
it can but adjusts the view's height to its own height to preserve the
original aspect ratio. The result is shown below.
The View protocol also defines modifiers that are particularly useful with
Image views. The following are the most frequently used.
The size of the view remains the same, but the size of the image is reduced
by half with the scaleEffect() modifier.
In addition to the modifiers available to specify the size of the view and
scale the image, we can also adapt it to the font size selected by the user
from Settings, as we did before with Dynamic fonts (see Listing 5-16). For
this purpose, SwiftUI includes the following property wrapper.
Because we use the @ScaledMetric property to define the size of the image, it
changes when the user selects a different size from Settings. The figure
below shows what we see when different sizes are selected (small on the
left, large on the right).

Symbols are loaded and displayed with an Image view, but we must
implement the initializer Image(systemName:) with the name of the symbol, as
in the following example.
SF symbols were designed to work with text and therefore their size can be
determined by a Font structure, and the style by the font's modifiers, as
shown next.
In the example of Listing 5-49, we apply the font() modifier with the system
font, a size of 100 points, and a weight of type semibold. Notice that we
declare the modifiers all in one line, but we could have defined the Font in a
constant and use that constant to apply it to the Image view, as in the
following example.
The code in Listing 5-50 initializes a Font structure with the system's font
and a size of 100, and then modifies an Image view with this font and a
weight of type semibold. This is the same as before, but makes our code
easier to read.
No matter how our code is organized, the view always shows the symbol of
an envelope on the screen.
SF Symbols come in different versions. For instance, the symbol with the
name "envelope" implemented in our example has a version with a circle
around it, another with a badge, and more. These are called Variants and
are specified after the symbol's name using dot notation, as in
envelope.circle, or envelope.fill. All the variants of a symbol can be found
in the SF Symbol application, but the framework also includes the following
modifier to specify a variant.
This is the same as creating the Image view with the "envelope.fill" string,
but selecting the variant from a modifier allows us to modify the symbol
and animate the changes according to changes in the state of the view, as
we will see later.
SF Symbols are displayed in the color and size of the font, but some
symbols can include up to three more colors, and all of them can be scaled
up or down. SwiftUI includes the following modifiers for this purpose.
symbolRenderingMode(SymbolRenderingMode)—This
modifier sets the symbol's rendering mode. The argument is a
structure with the type properties hierarchical, monochrome (default),
multicolor, and palette.
imageScale(Scale)—This modifier sets the symbol's scale. The size is
determined from the size of the font and the scale specified by the
attribute. The attribute is an enumeration with the values small,
medium, and large.
The microphone is displayed with the color by default (the font color or the
foreground color assigned to the view), and the badge is green. These are
the symbol's original colors, but we can change them by specifying the
hierarchical or palette modes. The colors for these modes are specified by the
foregroundStyle() modifier, as in the following example.
Some SF Symbols can have variable colors to represent different states. For
instance, some symbols include graphics resembling radio waves that
change color to represent different signal levels. To implement these
symbols, we must initialize the Image view with the variableValue
argument. The value of this argument determines the variety of the symbol
to be shown. The value required to jump from one state to another
depends on the number of states the symbol can take. In the following
example, we display a symbol with 5 states.
Listing 5-54: Displaying a variable SF Symbol

struct ContentView: View {
var body: some View {
Image(systemName: "dot.radiowaves.forward", variableValue: 0.8)
.font(.largeTitle)
}
}

The value of the variableValue argument must be below the threshold for
each state. The symbol implemented in the view of Listing 5-54 have 5
states and therefore we just have to find a value that is below the
threshold for the state we want to display. The following Figure shows
possible values for this symbol and all the states it can take.
Although we can combine Text views with symbols to build the interface, as
we will see later, SwiftUI includes a view called Label to show a text along
with an image. This is specially useful with SF Symbols because they can
automatically adapt to the size and style of the text. The structure includes
the following initializers and modifier to create these views.
A Label view determines the text and the image to be shown, but we can
use the font() modifier to specify the characteristics of the font, as shown
next.

Event Modifiers

Besides the modifiers to change the styles and format of the views, SwiftUI
includes modifiers to respond to events. These events can be produced by
the user, such as when the user touches the screen with a finger, or by the
system, such as when information is received from the network. There are
multiple modifiers available to process events, some are generic, others
more specific. For instance, the View protocol defines the following
modifiers to perform a task when a view appears or disappears from the
screen.
These modifiers are applied like any other, but unlike the rest, they are
executed when the system detects the event to which they respond. For
instance, in the following example we print a message on the console with
the onAppear() modifier, but the text is not printed until the view appears on
the screen.
The code in Listing 5-56 prints a message on the console with the value of
a constant used to determine the size of the font. It is not a practical
example, but it shows how to implement these modifiers. Besides the
onAppear() and onDisappear() modifiers, SwiftUI includes others to process
gestures, like the onTapGesture() modifier used to detect a tap on the view,
and others even more specific, like the onReceive() modifier used to receive
data emitted by a publisher, but all of them work in a similar way; they
perform a task when the event is detected by the system. We will see more
practical examples of the onAppear() modifier and work with other event
modifiers in later chapters.
Custom Modifiers

Interfaces with single views, like those we have created so far, are the
exception. User interfaces are created from the combination of multiple
views. This means that more often than not we will find ourselves applying
the same modifiers over and over again. In cases like this, we can avoid
repetition by implementing custom modifiers. Custom modifiers
encapsulate multiple modifiers in a single structure that we can apply later
to the views with the modifier() modifier. The structure must conform to the
ViewModifier protocol and implement a method called body that receives a
parameter of type Content. The parameter represents the views we want to
modify and, therefore, it is to this parameter that we apply the actual
modifiers, as shown next.
init(size: CGFloat) {
self.size = size
}
func body(content: Content) -> some View {
content
.font(Font.system(size: size).weight(.semibold))
.foregroundColor(Color.blue)
}
}
struct ContentView: View {
var body: some View {
Image(systemName: "envelope.circle")
.modifier(MyModifiers(size: 50))
}
}

5.3 Layout

The closure assigned to the body property must return only one view. We
haven't had any issues so far because all our examples have returned only
a Text view or an Image view, but a useful user interface requires the
implementation of multiple views. Some work as containers, others display
content, and there are several views designed to process user input.
Therefore, to create the interface, we must be able to group multiple views
in one single view and arrange them on the screen. The solution proposed
by SwiftUI is to work with stacks.
Stacks


By default, the views inside a VStack are aligned to the center, but we can
change that with the alignment argument. In addition to center, the
argument can take the values leading and trailing, which mean left and right
when the device is configured with a left-to-right language like English.
Figure 5-79: Vertical stack aligned to the left with a space of 20 points in
between
The HStack view works in a similar way. The views are declared on a list, one
after another, and the compiler takes care of creating the code to display
them side by side.
By default, the views in a horizontal stack are aligned to the center and
positioned with a standard space in between (usually 8 points).

An HStack view includes the same arguments as a VStack for alignment and
spacing, but the alignment argument specifies the vertical alignment. This
is useful when the stack is composed of views of different heights, as in our
example. The height of the stack is determined by the height of its tallest
view and the rest of the views are aligned according to the argument's
value. Figure 5-81, below, shows all the possible vertical alignments. In this
example, we reduced the width of the stack to force the Text view to display
the text in two lines, which allows us to show how the firstTextBaseline and
the lastTextBaseline alignments work.
Besides the vertical and horizontal stacks, SwiftUI also provides the ZStack
view to overlay the views. The views appear on the screen in front of each
other in the same order they are declared in the stack.
If we ignore the alignment argument, the views are aligned to the center,
but there are other alignments available. The values are bottom,
bottomLeading, bottomTrailing, center, leading, top, topLeading, topTrailing, and trailing.
When views overlap, like those included in a ZStack view, the system
determines the order in which they appear on the screen from the order in
the code. The first view is drawn first, then the second view is drawn in
front of it, and so on. The View protocol defines the following modifier to
change this order.
The Text view in Listing 5-64 includes a yellow background, so we can see its
position in the Z axis. The Image view should be drawn first, and then the
Text view should cover most of the image, but because we set an index of
-1 for the Text view, it is drawn in the back.

When we group views with a stack, we can apply modifiers to all the views
at the same time by assigning the modifiers to the stack instead of the
individual views. For instance, if we want to assign the same color to the
Text view and the Image view of the previous example, we can apply the
foregroundColor() modifier to the ZStack view.
The code in Listing 5-66 defines a Vstack view inside an HStack view. The
VStack includes two Text views aligned to the left and with different styles.
The alignment options available for VStack and HStack views align the views
in the perpendicular axis. A vertical stack can align the views horizontally,
and a horizontal stack can align the views vertically. To align the views on
the same axis, we must add a flexible space. SwiftUI includes the Spacer
view for this purpose.
The system calculates the widths of the image and the stack, and then
assigns the rest of the space available to the Spacer view in the middle.

A Spacer view can be positioned anywhere on the list, not only between
views. This is useful when we want the views to be at the top or the
bottom of the screen. For instance, we can embed the HStack of previous
examples in a VStack and add a Spacer at the bottom to move our views to
the top of the screen.
Listing 5-68: Aligning the views to the left and the top

struct ContentView: View {
var body: some View {
VStack {
HStack {
Image(systemName: "cloud")
.font(.system(size: 80))
VStack(alignment: .leading) {
Text("City")
.foregroundColor(.gray)
Text("New York")
.font(.title)
}
Spacer()
}
Spacer()
}
}
}

In this example, we use two Spacer views, one at the end of the HStack to
move the views to the left, and another at the end of the main VStack to
move the HStack to the top. This Spacer view takes all the space available at
the bottom, moving the rest of the views up.
The system defines a layout guide called Safe Area where we can place the
content of our interface. This is the area determined by the space
remaining in the window after all the toolbars and special views are
displayed by the system (including the notch at the top of modern
iPhones). That's the reason why there is a space between the view of our
previous example and the top of the screen (see Figure 5-88). The white
bar at the top is the space occupied by the system's toolbar. Although it is
recommended to always build the interface inside the safe area, the View
protocol provides the following modifier to ignore it.
There are two safe areas, one called container that determines the space
available inside the window after all the navigation bars and toolbars are
displayed, and another called keyboard that determines the remaining space
after the virtual keyboard becomes visible.
In this example, we ignore all the safe areas and, therefore, our views
extend to the edges of the screen, but we can ignore only the container
safe area but not the keyboard, so when the virtual keyboard opens, it
doesn't overlap the views.
Listing 5-70: Ignoring only the container safe area at the bottom

struct ContentView: View {
var body: some View {
VStack {
Spacer()
HStack {
Image(systemName: "cloud")
.font(.system(size: 80))
VStack(alignment: .leading) {
Text("City")
.foregroundColor(.gray)
Text("New York")
.font(.title)
}
Spacer()
}
}.ignoresSafeArea(.container, edges: .bottom)
}
}

in this example, we move the Spacer view to the top of the vertical stack to
push the content down. We ignore the safe area again, so the content is
placed right at the bottom of the screen, but because we ignore only the
container safe area at the bottom, if later we add an element that opens the
keyboard, the views will move up to remain visible.
SwiftUI includes a modifier to expand the safe area. The modifier changes
the safe area inset to include additional space.
This modifier may be used to make sure that important views at the edge
of the screen are always visible, or to create our own navigation bars, as
shown next.
Listing 5-71: Expanding the safe area

struct ContentView: View {
var body: some View {
VStack {
Spacer()
HStack {
Image(systemName: "cloud")
.font(.system(size: 80))
VStack(alignment: .leading) {
Text("City")
.foregroundColor(.gray)
Text("New York")
.font(.title)
}
Spacer()
}
}
.safeAreaInset(edge: .bottom, content: {
HStack {
Spacer()
Text("Important")
.padding()
Spacer()
}.background(.yellow)
})
}
}

In this example, we add an HStack view at the bottom of the safe area. This
creates a yellow bar at the bottom of the screen with the text "Important",
but because this view is part of the safe area, the rest of the content is
shown on top.
Stacks divide the space equally among the views, but we must decide what
to do when there is not enough room to show them all. By default, the
system assigns a fixed size to images and reduces the size of Text views to
make them fit, as shown next.
This example creates an HStack with three views: a text, an image, and
another text. In an iPhone SE in portrait mode, where there is no room to
display them all, the system preserves the image's original size but
compresses the Text views to make them fit in the remaining space.

Because the Text views are limited to only one line, the system truncates
the texts. If what we want is to show one of the texts in full, we must
assign a higher priority to it. The View protocol defines the following
modifiers for this purpose.
The code in Listing 5-73 assigns a priority of 1 to the second Text view. Now
the system calculates the space required by this view first and therefore
the "New York" text is shown in full.
This is the same code as before, but now we assign the fixedSize() modifier
to the first Text view. In consequence, this view is going to adopt the size of
its content and the "Manchester" text will be shown in full, regardless of
the priority of the rest of the views.

Alignment Guides

The following example aligns three images of different sizes. They are all
100 points wide, but the signbus image is 200 points tall, the signplane
image is 170 points tall, and the signphone image is 220 points tall. (The
images are available on our website.)
Figure 5-96 shows our three images aligned to the center. In this example,
we applied a blue border to the stack to make it easy to see the changes
produced by the alignment and added a red line on top of the picture to
visualize the common point of alignment chose by the stack.
As illustrated in Figure 5-97, the center alignment by default for the bus is
100 points (half its height), but the wheels are positioned at 118 points. If
we want to center the image at this point, we must add 18 points to the
image's natural center, as shown next.
The alignmentGuide() modifier requires two values. The first one is a value
that represents the type of alignment we want to modify. In this case, we
are aligning the views to the center, so we modify the VerticalAlignment.center
type. The second value is a closure that must return the new value for this
type of alignment. The closure receives a value of type ViewDimensions. This
is a structure with two properties, width and height, to return the current
width and height of the image, and also includes the definition of a
subscript, which allows us to get the values for each alignment guide using
square brackets and the alignment as the key. In the example of Listing 5-
76, we get the current value of the VerticalAlignment.center key for the view,
add 18 to it and return the result. From that moment on, the center
alignment for this view will return 118 instead of 100, so the view is
aligned 18 points higher.

If we want to align all the images by the bottom of the graphic, we must
modify the alignment guide for each Image view.
Listing 5-77: Aligning all images to the center with custom values

struct ContentView: View {
var body: some View {
HStack(alignment: .center) {
Image("signbus")
.alignmentGuide(VerticalAlignment.center) { dimension in
dimension[VerticalAlignment.center] + 18 }
Image("signplane")
.alignmentGuide(VerticalAlignment.center) { dimension in
dimension[VerticalAlignment.center] + 68 }
Image("signphone")
.alignmentGuide(VerticalAlignment.center) { dimension in
dimension[VerticalAlignment.center] + 89 }
}.border(Color.blue, width: 2)
}
}

The code in Listing 5-77 declares the closures as trailing closures and omits
the return keyword to simplify the code, but the process is the same. The
image of the bus is 200 points tall, its default center is at 100 points, but
the base of the bus is at 118 points, so we add 18 points to the current
center alignment (118 - 100). The image of the plane is 170 points tall, its
default center is at 85 points, but the base of the plane is at 153 points, so
we add 68 points to the current center alignment (153 - 85). And we do the
same for the phone. The image is 220 points tall, its default center is at 110
points, but the base of the phone is at 199 points, so we add 89 points to
the current center alignment (199 - 110). As a result, we get all the images
aligned by the graphic's baseline.
So far, we have modified the alignment guides of views that belong to the
same stack. If our interface requires us to align views from different
containers (stacks), we must define custom alignment types. Custom
alignment types are defined as extensions of the alignment structures
(VerticalAlignment and HorizontalAlignment). We worked with extensions before
in Chapter 3. They add functionality to an existing data type (see Listing 3-
180). In this case, we need an extension to add a custom alignment guide.
For this purpose, the extension must include an enumeration that
conforms to the AlignmentID protocol, which requires the implementation of
a method called defaultValue to return the alignment's default value. The
extension must also include a type property which sole purpose is to
simplify the declaration of the alignment, as shown in the following
example.
extension VerticalAlignment {
enum BusAlignment: AlignmentID {
static func defaultValue(in dimension: ViewDimensions) -> CGFloat {
return dimension[VerticalAlignment.center]
}
}
static let alignBus = VerticalAlignment(BusAlignment.self)
}
struct ContentView: View {
var body: some View {
HStack(alignment: .alignBus) {
VStack {
Image("signbus")
}
VStack(alignment: .leading) {
Text("Transportation")
Text("Bus")
.font(.largeTitle)
}
}.border(Color.blue, width: 2)
}
}

This code defines an extension for the VerticalAlignment structure. We call the
enumeration BusAlignment because we use it to align the image of the bus.
Its default value was defined as the current value of the center alignment.
After this, we define a type property called alignBus that returns an
alignment of this type. Notice that the value provided to the
VerticalAlignment's initializer is a reference to the definition of the BusAlignment
enumeration, not an instance of it (see Listing 3-143).
Our ContentView view includes two VStack views embedded in an HStack view,
one for the image of the bus and another with two Text views. The custom
alignBus alignment defined at the beginning is assigned to the HStack, and
therefore the VStack views are aligned to the center.
The red line drawn in front of the picture in Figure 5-100 shows the
common point of alignment. The HStack calls the defaultValue method for
each VStack view, gets in return the value of their current center alignment,
and therefore it aligns the VStack views to the center.
Of course, we can change this alignment by modifying the alignment
guides of the views. For instance, if we want to position the "Bus" text in
line with the bus's window, we must move the alignBus alignment for those
views. The alignment for the bus image has to be at the center of the bus's
window and the alignment for the text has to be at the center of the word.
The center alignment of the image of the bus is at the position 100, and the
center of the bus's window is at the position 60, so to move the alignment
point to the center of the window we must subtract 40 to this view's center
alignment. For the text is simpler, we just have to return the value of the
current center alignment, as in the following example.
Listing 5-79: Aligning views with custom alignment guides

import SwiftUI
extension VerticalAlignment {
enum BusAlignment: AlignmentID {
static func defaultValue(in dimension: ViewDimensions) -> CGFloat {
return dimension[VerticalAlignment.center]
}
}
static let alignBus = VerticalAlignment(BusAlignment.self)
}
struct ContentView: View {
var body: some View {
HStack(alignment: .alignBus) {
VStack {
Image("signbus")
.alignmentGuide(.alignBus) { dimension in dimension[VerticalAlignment.center] - 40 }
}
VStack(alignment: .leading) {
Text("Transportation")
Text("Bus")
.font(.largeTitle)
.alignmentGuide(.alignBus) { dimension in dimension[VerticalAlignment.center] }
}
}.border(Color.blue, width: 2)
}
}

The definition of the custom alignment is the same as before, but now we
modify the values for each view we want to move with the alignmentGuide()
modifier. The alignment for the image of the bus is moved 40 points up the
center line, and the alignment for the Text view is moved to its center
alignment, so we get the views right where we want them.
The purpose of Group views is to group views together. We can use them to
split large lists of views into groups of 10 or less to avoid the issue
mentioned above, but also for other purposes, such as applying styles to
several views at the same time, as in the following example.
The closure assigned to the body property can only return one view, so the
compiler is able to determine the data type of the view and process the
value correctly. If we try to use an if else instruction to return different views
depending on a condition, we will get an error. But we can use a Group view
to solve this issue. The solution is to insert the conditional statement inside
a Group view and return that view instead.
Usually, the views to show are selected depending on the current state. We
will learn how to work with view states in the next chapter, but we can test
it with a simple condition, as shown next.
This code defines a Boolean constant to select the view. If the constant is
true, we show an Image view with an SF Symbol, otherwise, we show a Text
view. Therefore, the view displayed on the screen is selected at run time,
depending on the value of the valid constant, but because we embedded
the views in a Group view, the compiler can identify the value returned and
therefore the code is functional.
Grids

We can embed one stack into another as many times as needed to achieve
the design we are after, but SwiftUI defines an additional container view
called Grid for this purpose. A Grid view can distribute static content in
multiple rows and columns. The following is the view's initializer.
This example creates a grid with two rows and two columns. The rows are
defined by two GridRow structures and the columns by the views inside
them. (In this case, two Image views per row.)
Figure 5-104: Grid
In this example, the first row includes only one cell defined by a Text view,
but because we apply the gridCellColumns() modifier with the value 2, the
view occupies two columns.
Figure 5-105: Multicolumn cell
In this example, the grid contains two columns, the one on the left with an
image and the one on the right with another grid, which in turn contains
two rows. Therefore, the two cells on the right share the same row with
the cell on the left.
Figure 5-106: Multiple grids

Custom Views

The code required to define the user interface can grow considerably as we
build our application. When we reach a certain level of complexity, we
must think about refactoring (reorganizing our code). The pattern
proposed by SwiftUI involves breaking down the views into smaller pieces.
For instance, we can define the second grid in our previous example as a
separate view, so the code will be easier to read and maintain. One
alternative is to extract the view. The option is available when we hold the
Command key and click on the structure's name.
Once we select the Extract Subview option, a new view is created and
placed at the bottom of the file. The view is assigned the name
ExtractedView, but we can change it to one that better represents our view.
The new view also conforms to the View protocol and implements the body
property, as any other SwiftUI view. Once defined, we initialize it and the
system takes care of creating the views and place them in the right
location.
The views created with the Extract Subview option are defined in the same
file, but in most cases it is better to move them to their own file. The
option to create a new file is available from the File menu (File/New/File...)
or by pressing Command + N. Xcode offers two templates to create Swift
files, one for common Swift files, used to store Swift code, and another for
SwiftUI views. We can find them in the iOS tab, under the names Swift File
and SwiftUI View.
Although they have different names, they are both Swift files and are
created with the same extension (.swift). The only difference between the
two is the code included by Xcode. A Swift file only includes an import
statement for the Foundation framework, while a SwiftUI View file includes a
View structure with a simple view inside and a PreviewProvider structure to
create the preview on the canvas. The View structure takes the name
assigned to the file. For instance, if we want to create a SwiftUI View file to
store the ExtractedView structure from the previous example, we must call
the file ExtractedView.swift, so Xcode creates the structures with the right
name. (It is recommended to write the name in capital letters to match the
structure's name.) Notice that if we store a view in a separate file, we must
import the SwiftUI framework again or the SwiftUI views won't be
recognized.
Custom Layout

With stacks and grids we can organize our views as needed and create any
structure we want, but there are times when the interface requires that
unique touch that makes it special. For this purpose, SwiftUI includes
custom layouts. Custom layouts allow us to specify the exact position of
each view in a container. They are created with structures that conform to
the Layout protocol. The following are the two methods required by the
protocol.
VStack(alignment: .leading) {
Toggle(isOn: $selected, label: {
Text(selected ? "Custom" : "Standard")
}).padding(.bottom)
SelectedLayout {
Group {
Text("First")
.padding(10)
.background(.red)
.cornerRadius(10)
Text("Second")
.padding(10)
.background(.red)
.cornerRadius(10)
Text("Third")
.padding(10)
.background(.red)
.cornerRadius(10)
}
}
Spacer()
}.padding()
.font(.title)
}
}

This code defines a State property of type Bool and adds a Toggle button to
the interface to change the property's value. When the value of the
property is true, we show the custom layout, otherwise, we show the views
with a standard VStackLayout layout aligned to the left.
if valid {
myView = AnyView(Image(systemName: "keyboard"))
} else {
myView = AnyView(Text("The state is invalid"))
}
return myView
}
}

The ContentView structure in Listing 5-88 includes a method called getView()
that returns a structure of type AnyView. This structure is defined according
to the value of a Boolean property. If the value is true, we create an Image
view, otherwise, we create a Text view, but both views are inside an AnyView
view, so we always return the same type of view. When the content of the
body property is processed, the method is called, and the view returned by
the method is displayed on the screen.
The AnyView view is a wrapper we can use to pass different views around.
The problem is that the views lose their identity. The system considers the
views wrapped in an AnyView view to be the same, which affects
performance. A better alternative is processing the views before they are
returned by the method with the @ViewBuilder property wrapper. We
introduced this property wrapper before. It is used to construct the views
returned by Content closures (e.g., the closures assigned to the body
property). The good news is that we can also apply it to our custom
methods to be able to return different views without having to use a
wrapper, as in the following example.
if valid {
Image(systemName: "keyboard")
} else {
Text("The state is invalid")
}
}
}

The code in Listing 5-89 applies the @ViewBuilder property wrapper to the
getView() method, so now we can use this method as a Content closure and
produce any type of view we want without using a wrapper. (We will learn
more about property wrappers in Chapter 6.)
When the views returned by a method are dynamically selected, as we did
in the previous examples, we may not always be able to provide one. For
cases like this, SwiftUI includes the EmptyView view.
The EmptyView view works like any other, but it doesn't provide any content
and it has no size, so it doesn't affect the interface. The following example
creates an empty view when a condition is not met.
if valid {
Image(systemName: "keyboard")
} else {
EmptyView()
}
}
}

5.4 Previews

Xcode automatically builds the app and shows on the canvas a preview of
the view we are currently working on. If we introduce large changes to the
code, we must press the Resume button at top of the canvas to tell Xcode
to resume the preview, but otherwise the process is automatic.
Preview Modifiers

previewInterfaceOrientation(InterfaceOrientation)—This
modifier defines the orientation of the preview. The argument is a
structure with the type properties portrait, portraitUpsideDown,
landscapeLeft, and landscapeRight.
The preview is configured to work with the device selected in the scheme
from the Xcode's toolbar (see Figure 5-9), but we can change it with the
previewDevice() modifier.
This example configures the preview to represent an iPhone 13, but we can
include additional previews in the canvas for different devices by
embedding the views in a Group view.
Listing 5-93: Adapting the size of the preview to the size of the view

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewLayout(.sizeThatFits)
}
}


Environment

In the code of Listing 5-94, we assign a dynamic color called MyColor to the
Text view, and then set the appearance for the preview to dark. The
example assumes that we have added a Color Set to the Asset Catalog
called MyColor and assigned different colors for the modes Any/Dark, as
we did before (see Figure 5-44). The environment() modifier assigns the value
dark to the environment's colorScheme property, changing the view's
appearance to dark, so the preview displays the text in the color we have
selected for that appearance.
Do It Yourself: Create a Color Set in the Asset Catalog with the name
MyColor and assign different colors for the Any and Dark
appearances, as we did in the example of Figure 5-44. Update the
ContentView.swift file with the code in Listing 5-94. On the canvas,
you should see the text in the color selected for the dark
appearance. Remove the environment() modifier and resume the
preview. Now, you should see the text in the color selected for Any.
The Text view with the original title represents the initial state of our
interface. The state is updated with each character the user types in the
input field (Figure 6-1, left), and when the button is pressed, the interface
enters a new state in which the title inserted by the user has replaced the
original title and the color of the text has changed (Figure 6-1, right).
Every time there is a change of state, the views must be updated to reflect
it. In previous systems, this required the code to keep the data and the
interface synchronized, but in a declarative syntax all we have to do is to
declare what the configuration of the views should be in each state and the
system takes care of generating all the code necessary to respond to those
changes.
The possible states the interface can go through are determined by the
information stored by the app. For instance, the characters inserted by the
user in the input field and the color used in our example are values stored
by the app. Every time these values change, the app is in a new state and
therefore the interface is updated to reflect it. Establishing this
dependency between the app's data and the interface demands a lot of
code, but SwiftUI keeps it simple using property wrappers.
Property Wrappers

Property wrappers are a tool provided by the Swift language that allows us
to encapsulate functionality in a property. They are like the computed
properties introduced in Chapter 3 (see Listing 3-41), but applicable to
multiple properties. As other Swift features, they were designed to simplify
our code. For instance, we can define a property wrapper that limits the
value of a property to a certain range. All the properties declared with it
will only accept values between those limits.
A property wrapper is just a structure, but it must be preceded by the
@propertyWrapper keyword and include a property with the name
wrappedValue to process and store the value. The structure must also include
an initializer for the wrappedValue property. The following is a Playground
example that illustrates how to define a property wrapper that limits the
value of a property to a minimum of 0 and a maximum of 255.
@propertyWrapper
struct ClampedValue {
var storedValue: Int = 0
var min: Int = 0
var max: Int = 255
func printMessage() {
print("First Price: \(firstPrice)") // "First Price: 0"
print("Second Price: \(secondPrice)") // "Second Price: 255"
}
}
var purchase = Price(firstPrice: -42, secondPrice: 350)
purchase.printMessage()

The Price structure in Listing 6-2 includes two properties that use the
ClampedValue property wrapper, firstPrice and secondPrice, and a method to
print their values. The instances of the structure are initialized with the
values -42 and 350. Both values exceed the limits established by the
property wrapper, so the value stored for each property is the limit they
exceeded (0 for firstPrice and 255 for secondPrice).
The code in Listing 6-3 declares a @State property called title of type String
and initializes it with the value "Default Title". In the body of the view, we
show the value of this property with a Text view within a vertical stack.
Below the text, we include a Button view. We will study Button views later,
but for now all we need to know is that a Button view displays a label and
performs an action when the label is tapped by the user. We defined the
label as a Text view with the text "Change Title", so the user knows that it
has to press that button to change the title, and in the closure assigned to
the action we change the value of the title property to "My New Title".
The title property created with the @State property wrapper is used in two
places, first in the Text view to show the current value to the user, and then
in the action for the Button view to modify its value. In consequence, every
time the button is pressed, the value of the title property changes, the
@State property wrapper notifies the system, and the content of the body
property is automatically refreshed to display the new value on the screen.
Figure 6-2: Initial state (left) and state after the button is pressed (right)
All this process works automatically. We don't have to assign the new value
to the Text view or tell the view that a new value is available, it's all done by
the @State property wrapper. And we can include all the @State properties
we need to store every state of the interface. For instance, the following
example adds a @State property of type Bool to our view to determine
whether the user already inserted a new title or not and assign a different
color to the text.
Listing 6-4: Defining multiple states

struct ContentView: View {
@State private var title: String = "Default Title"
@State private var titleActive: Bool = false
The titleActive property stores a Boolean value that determines the color of
the text, so we can check its value to assign the appropriate color. In this
example, we use a ternary operator (see Listing 2-51). Using a ternary
operator to set the state of the view is the recommended practice because
it allows the system to determine all the possible states the view can
respond to and produce a smooth transition from one state to another. if
the value of titleActive is true, we assign the color red to the foregroundColor()
modifier, otherwise, we assign the color gray.
In the button's action, besides assigning a new value to the title property,
we now assign the value true to the titleActive property to change this state.
The titleActive property informs the system that there is a new value
available, the system refreshes the views, the ternary operator is evaluated
again, and in consequence the color red is assigned to the text.
IMPORTANT: There are two states in the example of Listing 6-4, and
both change at the same time, but the system takes into
consideration these situations and makes sure that the interface is
updated only when necessary.
A @State property creates a dependency between itself and the view and
therefore the view is updated every time its value changes. It is said that
the view is bound to the property. The binding we have used so far is
unidirectional. If the property is modified, the view is updated. But there
are views which values are modified by the user and therefore they must
be able to store the value back into the property without the code's
intervention. For this purpose, SwiftUI allows us to define a bidirectional
binding. Bidirectional bindings are declared by prefixing the name of the
property with the $ sign.
The views that usually require bidirectional binding are control views, such
as those that create switches the user can turn on and off, or input fields to
insert text. The following example implements a TextField view to illustrate
this feature. A TextField view creates an input field. The values required by
its initializer are a string with the text we want to show as placeholder and
the binding property we are going to use to store the value inserted by the
user. (We will learn more about TextField views and other control views
later.)
In this example, we add to the view the @State property we need to store
the text inserted by the user, and then define a TextField view between the
title and the button. The TextField view was initialized with the placeholder
"Insert Title", and the new titleInput property was provided as the binding
property for the view ($titleInput). This creates a permanent connection
between the TextField view and the property, so every time the user types
or removes a character in the input field, the new value is assigned to the
property.
In the action for the Button view, we introduced two modifications. First, we
assign the value of the titleInput property to the title property. This effectively
updates the title of the view with the text inserted by the user. And finally,
we assign an empty string to the titleInput property to clear the input field
and leave it ready for the user to start typing again.
In this example, the VStack view contains an input field and the button, as
before, but the title is now managed by an instance of the new HeaderView
structure defined at the bottom. In this view, we include the same Text view
as before, but add a @Binding property to be able to access the title property
defined in the ContentView structure.
A @Binding property always receives its value from a @State property, so we
don't have to assign a default value to it, but the connection created
between them is bidirectional, so we have to remember to prefix the @State
property with the $ sign to connect the @Binding property with it
(HeaderView(title: $title)).
Because of the bidirectional binding created between the @Binding property
and the @State property, every time the button is pressed, the changes are
detected by the system, and the body property of the HeaderView structure is
processed again to show the new values on the screen.
The wrappedValue property stores the value we assign to the @State property,
like the "Default Title" string assigned to the title property in the last
example. The projectedValue property stores a structure of type Binding that
creates the bidirectional binding we need to store a value back to the
property. If we read the @State property directly (e.g., title), the value
returned is the one stored in the wrappedValue property, and if we prefix the
property's name with a $ sign (e.g., $title), we access the Binding structure
stored in the projectedValue property. This is how SwiftUI proposes we work
with @State properties, but in theory we can also access the properties
directly, as in the following example.
This is the same example as before, but instead of using the SwiftUI
shortcuts, we read the wrappedValue and projectedValue properties of the State
structure directly. Of course, this is not necessary, but may be required
sometimes to overcome SwiftUI's shortcomings. For instance, SwiftUI
doesn't allow us to access and work with @State properties outside the
closure assigned to the body property, but we can replace one State structure
by another. For this purpose, the State structure includes the following
initializers.
For example, if we want to assign an initial value to the input field of our
previous example, we can add an initializer to the ContentView structure and
use it to assign a new State structure to the property.
We can access the values of a @Binding property the same way we do with a
@State property. If we just read the property, as we did in Listing 6-7, the
value returned is the value stored in it, and if we prefix the name with a $
sign, the value returned is the Binding structure the property wrapper uses
to establish the bidirectional binding with the view. But if we prefix the
name of the @Binding property with an underscore (e.g., _title), the value
returned is not a State structure but another Binding structure. This is
because a @Binding property wrapper is defined by a structure of type
Binding. Of course, the structure also includes properties to access the
values.
As we did with the State structure, we can access and work with the values
stored in the Binding structure. For instance, the following example
implements a separate view again to manage the title, as we did in Listing
6-6. This new HeaderView gets the value stored in the Binding structure from
the wrappedValue property, counts the total number of characters in the
string, and displays the result along with the title.
init(title: Binding<String>) {
_title = title
Figure 6-3: Title defined with the values of the @Binding property
There are many circumstances in which we may need a Binding value. For
instance, if we want to create a preview of the HeaderView, we must provide
a value for the title property. The following example illustrates how to
create a new Binding structure to provide this value and how to define the
preview for this view.
The code in Listing 6-10 instantiates a new Binding structure. The initializer
works like a computed property. It includes a getter and a setter. The getter
returns the current value and the setter receives the values assigned to the
structure. In this example, we always return the same string and since we
are not assigning new values to the structure, we just print the value on
the console. The instance is assigned to the constantValue constant and then
sent to the HeaderView structure, so the view has a value to show on the
canvas.
There is not much use for the Binding structure of this example other than
providing the value required by the HeaderView structure. In cases like this,
we can simplify the code with the constant() method. This type method
creates and returns a Binding structure with an immutable value, so we
don't have to create the structure ourselves.
Besides custom states, we can also respond to states set by the system in
the environment. We have worked with the environment before to
configure previews (see Environment in Chapter 5), but some of its
properties, like colorScheme and dynamicTypeSize, represent states that can
change while the app is running, so we can read their values and update
the views accordingly. For this purpose, SwiftUI includes the @Environment
property wrapper. This property wrapper works in a similar way to @State
but it is designed to report to the system changes in the environment
properties. The @Environment property wrapper is created with the
Environment structure. The following is the structure's initializer.
To create the model and keep the view up to date, we must define a class
that conforms to the ObservableObject protocol, define the properties we
want to use to store the states with the @Published property wrapper, store
an instance of this model with the @StateObject property wrapper, and then
include a property defined with the @ObservedObject property wrapper
inside every view we want to connect to this model.
The purpose of this application is to store the title of a book. The title
property stores the title, and the titleInput property is going to be used by
the view to allow the user to insert a new one.
IMPORTANT: The classes defined to store the data are a central part
of our application and can be large, so it is a good idea to store them
in a separate Swift file (see Figure 5-108). All you need to do is to
open the File menu at the top of the screen, select the New/File
options, and select the Swift File icon in the iOS panel. The file is
added with the rest, and the data types defined inside are accessible
from anywhere in the code.
Because the source of data (also called the source of truth) has to be
unique, we cannot create one object for each view, we have to create only
one object and then pass a reference to that object to all the views. Figure
6-7, below, illustrates this scheme. We store an instance of our model in a
@StateObject property and then pass a reference to the initial view, which in
turn passes that reference to the next view and so on.
Figure 6-7: Observable object model
The first view to receive a reference to the model should be the view
assigned as the root view of the window. In our examples, this is the
ContentView view, which is instantiated in the App structure. So we create an
instance of the ApplicationData class, store it in a @StateObject property, and
send a reference to the ContentView structure, as shown next.
@main
struct TestApp: App {
@StateObject private var appData = ApplicationData()
This is very similar to what we have done before, but instead of reading
and storing the values in @State properties, we do it in the @Published
properties of our observable object. When we store a value in a @Published
property, the property tells the system that new values are available, and
the views are automatically updated. The difference of working with
@Published properties in a model instead of @State properties is that if we
add new views to the interface later (additional screens the user can
navigate to), they will all have access to the same values and states.
There is another difference from previous examples we must consider. The
ApplicationData object that represents our model is passed to the instance of
the ContentView structure created for the application, but the
ContentView_Previews structure creates a separate instance of the ContentView
structure to show on the canvas, so we must provide an additional
ApplicationDataobject to this structure, as we did in Listing 6-15
(ContentView(appData: ApplicationData())). Now, the preview on the canvas can
also run the application.
Do It Yourself: Open the File menu at the top of the screen and click
on the New/File options to create a new Swift file (Figure 5-108).
Assign the name ApplicationData.swift to the file and replace the
code defined by the template with the code in Listing 6-13. Update
the code in your App structure with the code in Listing 6-14. Finally,
update the ContentView view with the code in Listing 6-15. You should
see something similar to Figure 6-8.
In the model of Listing 6-13, we define two properties, title to store the
actual information and titleInput to receive input from the user. Other views
added later to our application may need to access the title property to show
its value to the user, but the titleInput property is only required by the view
that contains the TextField view. This means that we are storing the private
state of a view in the app's model. Although there is nothing wrong with
this approach, it is recommended to use the model to store the app's data
but manage the states of the views from the views themselves. There are
different patterns we can implement to organize our application. One
approach is to define @State properties for each view, as we did before, but
a better alternative is to create additional observable objects. The
following is the model we need for this example.
Listing 6-16: Storing only the app's data in the model

import SwiftUI
Our model now only stores the title of the book. To manage the states of
the view and provide the binding properties for the TextField view, we can
define an additional observable object for the ContentView view, as in the
following example.
The initializer passes the reference of the model to the appData property
and then assigns the value of the model's title property to the titleInput
property of the contentData object, so the TextField view shows the current
value.
Figure 6-9: Text field initialized with the value in the model

Listing 6-19: Initializing the view's observable object when the views
appear

.onAppear {
contentData.titleInput = appData.title
}

Our application may have a view that presents a menu, another view that
displays a list of items, and another one that shows information pertaining
to the item selected by the user. All these views need to access the app's
data and therefore all of them must contain a reference to the model. But
passing this reference from one view to another until we reach the one
that requires the values, as illustrated in Figure 6-7, can be cumbersome
and error prone. A better alternative is to pass the reference of the model
to the environment and then read it from the environment whenever we
need it.
@main
struct TestApp: App {
@StateObject private var appData = ApplicationData()
There are two changes in this code from the previous example. Instead of
storing the reference of the model in an @ObservedObject property, we use
an @EnvironmentObject property. The reference works the same way, but the
@EnvironmentObject property automatically gets the reference from the
environment, so we don't have to pass it to the views anymore. And
second, an instance of our model was assigned to the environment of the
ContentView view created for the preview. This is because this ContentView
view does not belong to the hierarchy of the ContentView view created for
the application, and therefore they work with different environments.
Working with the values as they are provided by the model may cause
some issues. Something we must consider is that views usually need to
transform the values before presenting them to the user. But views in
SwiftUI should only be responsible of defining the interface. In addition,
multiple views may need to perform the same transformations on the data,
but doing so from different views can be error prone. For instance, we may
have an application that stores and displays the title and author of a book.
If the model stores the title and the author in different properties, we
would have to join the values in a single string every time they have to be
shown to the user. But if this process is performed from multiple views,
mistakes can be made. We may forget to join the strings and only show
one value, or do it in the wrong order. To avoid mistakes, the string should
not be created by the views but provided by the model, ready to use.
There are different patterns we can adopt to improve the organization of
our application. The one recommended by Apple is called Model View
View-Model (MVVM). In this pattern, there is a model with the basic
information, a view-model that prepares that information for the views,
and the views that present the information to the user.
struct Book {
var title: String
var author: String
}
struct BookViewModel {
var book: Book
var header: String {
return book.title + " " + book.author
}
}
class ApplicationData: ObservableObject {
@Published var userData: BookViewModel
init() {
userData = BookViewModel(book: Book(title: "Default Title", author: "Unknown"))
}
}

The code in Listing 6-22 replaces the model used in previous examples.
First, we define a structure called Book with properties to store the title and
the author of a book. Next, we define a structure called BookViewModel to
prepare the information for the view. This structure contains two
properties: the book property to store an instance of the Book structure and
a computed property called header to return a string that combines the
values of the book's title and author properties. Lastly, we define the
observable object with a @Published property to store this data (a single
book in this example).
Usually, the data is loaded from the web or a file, but in this case, we are
working with temporary data, so we initialize the observable object with a
new instance of the BookViewModel structure that contains an instance of the
Book structure with values by default. Now, we can access the model from
the views.
Listing 6-23: Accessing the model in an MVVM pattern

import SwiftUI
This view now accesses the model through the observable object and the
view model structure. To show the title and the author, we don't have to
combine the values of the title and author properties, we just read the header
property and the view model takes care of reading the values from the
model and create the string for us. All the work of formatting and
processing the values is performed in the view model, not the views.
To modify the values, we must follow the hierarchical chain. The title of the
book is stored in the title property of the Book structure. This structure is
available from the book property of the BookViewModel structure, which was
assigned to the userData property of the ApplicationData object. So, to modify
the title, we must write the appData.userData.book.title property, as we did in
the action for the Button view.
The function of the model in this pattern doesn't change much. Instead of
the observable object, the information is stored in the instance of the Book
structure, but because that instance is assigned to the instance of the
BookViewModel structure, which in turn is assigned to a @Published property of
the observable object, every time a value in the model is modified, the
@Published property reports the change to the system, the views are
updated, and the new values are displayed on the screen.
As we have already seen, the Button view creates a simple control that
performs an action when pressed. The following are some of the
structure's initializers.
This example includes a Text view and a Button view in a VStack. The Text view
displays always the same string with a background color defined by the
colorActive property. If the value of this property is true, we assign the green
color to the background, otherwise the color assigned is clear (transparent).
When the button is pressed, we toggle the value of this property, the body
property is evaluated again, and the text's background changes to the next
color (for more information on the toggle() method, see Listing 3-55).
The button in this example toggles the value of a @State property called
showInfo. Below the button, we check the current value of the property. If
the value is true, we show a Text view, otherwise, we do nothing. Therefore,
when the button is pressed, the value of the showInfo property changes, the
content of the body property is drawn again, and the Text view appears or
disappears, depending on the current value of the property.
The if else statements can also be used to perform the button's action only
when a condition is met, but we can also disable the button completely
with the following modifier.
The following example applies this modifier to disable the button after it is
pressed, so the user can perform the action only once.
This view includes two @State properties, one to keep track of the color, and
another to know whether the button is enabled or not. When the button is
pressed, the action assigns the value true to the buttonDisabled property and
the button stops working, so the user can press it only once.
As we have seen before, the initializers for the Button view can include the
label argument to define the label with any view we want. This argument is
very flexible. It can include views like Text views and Image views. Images in
buttons are displayed in the original rendering mode, which means they
are showing in the original colors, but there is another mode available that
creates a mask with the image and show it in the app's accent color or the
foreground color assigned to the control. To select the rendering mode, the
Image view includes the following modifier.
renderingMode(TemplateRenderingMode)—This modifier
defines the rendering mode for an Image view. The argument is an
enumeration with the values original and template.
The following example defines a button with an image and a text. The
renderingMode() modifier is applied to the Image view to show the image as a
template.
The view in Listing 6-29 includes a @State property called expanded to control
the width of the Text view. If the value of the property is true, we give the
view an infinite width with the infinity value, otherwise, we make it 150
points wide. Every time the user presses the button, the value of the
expanded property is toggled with the toggle() method and therefore the
width of the Text view changes.
We can also assign standard styles for the buttons with the following
modifiers.
These styles were designed to look good with SF Symbols. The advantage
of using SF Symbols instead of our own images is that they are scaled to
the size of the font assigned to the button. This, along with the possibility
of scaling the button itself with the controlSize() modifier, allows us to create
buttons of different sizes.
The following example defines a button that expands when pressed. The
styles include a padding and a green border. To apply these styles, we must
create a structure that conforms to the ButtonStyle protocol, implement the
makeBody() method, and return from this method the view we want to
assign to the body of the button.
The views that make up the body of the button are provided by the label
property of the Configuration structure, so we can read and modify the value
of this property to apply the new styles, as shown next.
The TextField view is another control we have introduced before. The view
creates an input field the user can interact with to insert a value (a line of
text). The following is one of the initializers included by the structure.
The framework defines a few modifiers for TextField views. The following are
the most frequently used.
We have already seen how to include a simple TextField view to get input
from the user, but we haven't applied any modifiers yet. The following
example shows how to style the view and how to capitalize words.
The style applied to the TextField view in Listing 6-34 is called roundedBorder.
This adds a border to the input field that makes the area occupied by the
view visible, as shown below.
Figure 6-21: Text field with a rounded border
Besides a button, the user normally expects to be able to save the value by
pressing the Done button on the keyboard. The framework includes the
following modifiers for this purpose.
The closure assigned to the onSubmit() modifier is executed when the focus
is on the view (e.g., the user is editing the input field). If we apply it to a
TextField view, the of argument can be omitted, as in the following example.
The code in Listing 6-35 implements the submitLabel() modifier to change the
title of the Done button to "Continue", and then adds a method to the
structure called assignTitle() that performs the same action as before. The
method is called from two places, the closure assigned to the onSubmit()
modifier and the action of the Button view, so the operation is performed
either when the button on the interface is pressed or when the keyboard's
Done/Return button is pressed. The value inserted in the text field is
always stored in the title property, regardless of the action the user decides
to perform.
When a view that can take input or process feedback from the user is
selected, it is said that the view is in focus. SwiftUI includes several tools to
process this state. We can process a task when a view gains focus, know if
the focus is on a view, or remove focus from a view. There are two property
wrappers available for this purpose: @FocusState and @FocusedBinding.
@FocusState stores a value that determines where the focus is at the
moment, and @FocusedBinding is used to pass the state to other views. To
manage the state, the framework includes the following modifiers.
The initial value of the @FocusState property is nil, which means no view is
focused. When the user taps on a text field, the focus moves to that view
and the value that identifies the view is assigned to the property. By
comparing this value with the values in the enumeration, we know which
TextField view is focused and can change the background color accordingly.
Notice that the roundedBorder style adds a border and a white background to
the text field, so only the background of the padding is visible in this
example.
In mobile devices, the virtual keyboard opens when a view that can
process the input gains focus (e.g., TextField view). The keyboard remains
open as long as the focus is on a view that can process the input. This
means that to close it, we must remove focus from the view. In SwiftUI this
is achieved by assigning the value nil to the @FocusState property, as shown
next.
Do It Yourself: Update the Button view in the ContentView view with the
code in Listing 6-38. You shouldn't be able to modify the title until
you have inserted a name and a surname.
Do It Yourself: Update the Button view with the code in Listing 6-39.
You shouldn't be able to press the Save button until you have
inserted a name and a surname.
Besides checking whether the properties contain a valid value, we can also
limit what the user can type on the fields. For instance, we could only
accept numbers or a specific amount of characters. For this, we need to
check whether the value inserted by the user is valid or not every time the
state of the view changes. The framework includes the following modifier
for this purpose.
This modifier can only check one value, so we should apply it to every view
we want to control. For instance, we can use it in the two TextField views of
our example to limit the number of characters the user is allowed to type.
If the user goes over the limit, we remove the extra characters and assign
the result back to the property, as shown next.
For this example, we need an additional property to store the last value
inserted by the user, so if the new value is not valid, we restore the state
back to the value stored in this property. To be able to update the value of
this property every time the user inserts a character in the field, we must
declare it in an external object. (The properties of a View structure cannot
be modified, unless they are state properties.) In this case, we have
decided to create an observable object and manage the values for the view
from it, including the states (see Listing 6-17). We have the title property to
store the title as before, the numberInput property to store the number
inserted by the user, and the currentNumber property to store the last value.
As before, the view includes a TextField with the onChange() modifier. The
difference is in how we check if the value is valid or not. We first check if
the text field is empty or not. If empty, we clean the currentNumber property,
otherwise, we check if the value is an integer with the Int() initializer. This
initializer returns nil if the string cannot be converted into an Int value, so
we compare the value returned by the initializer with nil and update the
properties accordingly. If the value is an integer (different than nil), we
update the currentNumber property with the number inserted by the user,
otherwise, we assign the value of the currentNumber property to the
numberInput property to update the text field with the last value inserted by
the user and remove the invalid characters.
Notice that we have also implemented the keyboardType() modifier to show
the appropriate keyboard for the input we are expecting from the user (in
this case, numbers).
By default, a TextField view only displays one line of text, but we can allow
the view to expand to include more with the lineLimit() modifier. (The same
modifier implemented before to expand Text views.) Besides applying the
modifier to set the number of lines we want, we also need to ask the view
to scroll the content in the vertical axis, as shown next.
In this example, the TextView view expands until it reaches a height of 5 lines
and then the text scrolls vertically to allow the user to keep typing. If we
want to give the view a minimum and a maximum size, we can declare the
modifier with a range, as in lineLimit(3...5).

SecureField View

SwiftUI also includes a view to create a secure text field. The view replaces
the characters inserted by the user with dots to hide sensitive information,
such as passwords.
The implementation is the same as with TextField views, and we can also
apply some of the same modifiers, as shown next.
The SecureField view looks the same as the TextField view. The only difference
is that the characters are hidden.
SwiftUI includes an additional view to allow the user to insert multiple lines
of text called TextEditor. The following is the view's initializer.
This view can take some of the modifiers we have already applied to
TextField and Text views to format the text. For instance, we can tell the view
how to align the text, the space we want between lines, and whether the
view should correct the text inserted by the user.
The Toggle view creates a control to switch between two states. By default,
it is displayed as a user-friendly toggle switch on mobile devices and as a
checkbox on Macs. The view includes the following initializers.
The view requires a Binding property to store the current value. In the
following example, we provide a @State property and use the value of this
property to select the proper label.
The code in Listing 6-45 uses a ternary operator to check the value of the
currentState property and display the corresponding text ("On" or "Off"). By
default, we set the value of the property to true, so the switch is activated
and the "On" label is displayed on the screen, but if we tap the switch, it is
turned off, the view is updated, and the "Off" label is shown instead.
The closure assigned to the label argument can include a second view to
define a subtitle, as in the following example.

The Toggle view creates a horizontal stack to contain the label and the
control with a flexible space in between, in consequence, the view
occupies all the horizontal space available in its container and the label and
the control are displaced to the sides. If we want to have absolute control
on the position and size of the views, we can apply the fixedSize() modifier
introduced before to reduce the view's size, or hide the label with the
following modifier.
This modifier works with several controls, but it is particularly useful with
switches. The following example shows how to implement it to define a
custom label for the control.
The view is now of the size of the control and centered on the screen. The
label is not shown anymore, so we declare it with an empty string, but
include a Text view on the side to display the current value.
Like Button views, Toggle views also implement a modifier to define the style
of the control.
The value by default is automatic, which means the style of the control is
going to be selected by the system. If we want to always apply the same
style, we can assign the values switch or checkbox (only available for Macs).
These values are used to specify standard styles, but the framework also
includes the value button to create a completely different type of control.
When we assign this style to the view, the system shows a toggle button to
represent the on and off states. When the button is in the on state, it is
highlighted, otherwise it is shown as a standard button.
The styles offered by the framework are limited, but we can create our
own. All we have to do is to define a structure that conforms to the
ToggleStyle protocol. The protocol's requirement is for the structure to
implement the following method.
There are a few things we must consider before customizing a Toggle view.
First, the label property of the Configuration structure contains a copy of the
view that represents the control's current label, so if we want to keep this
label, we must include the value of this property in the new content.
Second, a Toggle view is designed with an HStack view and a Spacer view
between the label and the control. If we want to stick to the standard
design, we must preserve this arrangement. And third, we are responsible
of responding to user interaction and update the state of the control, so
we must check for a gesture and change the control's state by modifying
the value of the isOn property when the gesture is performed by the user.
In the example of Listing 6-49, we define a structure called MyStyle and
implement the required makeBody() method to provide the new design for
the Toggle view. To preserve the standard design, we wrap the views with an
HStack view and separate the label and the control with a Spacer view. We
first read the value of the label property to include the current label, then
declare the Spacer view, and finally define an Image view that presents an SF
Symbol that looks like a checkbox. To turn this Image view into a control, we
define its size with a font() modifier, apply the foregroundColor() modifier to
change the color of the symbol depending on the current value of the isOn
property, and finally, use the onTapGesture() modifier to detect when the user
taps on the Image view. We will learn more about gesture modifiers in
Chapter 12. For now, all we need to now is that this modifier executes a
closure every time the view is tapped by the user. In this closure, we access
the Binding value of the isOn property and toggle its value by applying the
toggle() modifier to the Boolean value stored in its wrappedValue property. (In
this case, the setter of the Binding value is private, so we access it from the
wrappedValue property, as explained before in this chapter.) This modifies the
current value of the property, which changes the state of the control,
turning it on and off.

Slider View

A Slider view creates a control that allows the user to select a value from a
range of values. It is displayed as a horizontal bar with a knob that indicates
the selected value. The structure includes the following initializer.
The view in Listing 6-51 includes a new @State property called textActive. The
closure assigned to the onEditingChanged argument assigns the value true
to this property when the user begins moving the slider and the value false
when the user let the knob go. The background() modifier of the Text view
reads the value of this property to assign a different background color to
the view depending on the current state. Because of this, the text that
displays the slider's current value has a yellow background while the user is
moving the slider, or no color otherwise.

ProgressView View

SwiftUI includes the ProgressView view to create a progress bar. The view was
designed to show the progress of a task over time.
This ProgressView view goes from 0.0 to 10.0 and starts from the value 5 (the
value assigned to the currentValue property by default), so the progress bar is
set at the middle.
The ProgressView view was designed to show the progress of a task over
time, such as the amount of data currently downloaded from a server or
how far we are from finishing processing the data. We will learn how to
perform some of these tasks later, but for now we can test it with a Slider
view, as shown next.
In this example, we set the same values for the Slider and the ProgressView.
They go from 0 to 10, so every time we move the slider, the progress bar
shows the same value.

The ProgressView structure includes the following modifier to define the style
of the progress bar.
The style by default is automatic, which means that the view is going to be
shown as a linear progress bar, but we can specify the circular value to
create an activity indicator. This is a spinning wheel that indicates that a
task is in progress, but unlike progress bars, this type of indicator has no
implicit limitations, so we don't need to specify any values, as shown next.

Stepper View

The Stepper view works with floating-point values of type Float or Double, so
we format the value again to display only integers. The result is shown
below.
Like the Toggle view, the Stepper view is implemented with a horizontal stack
and a flexible space between the label and the control. If we want to
provide our own label and define a custom position for the control, we
need to apply the labelsHidden() modifier, as we did in Listing 6-47. The
following example defines a custom label and creates the view with the
onIncrement and onDecrement arguments to display an arrow on the
screen that tells the user whether the last value was incremented or
decremented.

GroupBox View

SwiftUI includes a view called GroupBox to create a box around the views.
The view is defined with a background color and round corners to visually
group views and controls. The following is one of the view's initializers.
For didactic purposes we have defined all the views in the same file. In
the ContentView view, we specify the basic layout of our app. The view
creates two layouts according to the horizontal size class. If the size class is
compact, which means that the interface is being presented in a small space,
we create a VStack to show the views on top of each other, otherwise, we
create an HStack to show them side by side.
Organizing the views on the screen is not enough to adapt the
interface to the space available, sometimes we also must adapt their
content. In this case, we do it by passing a value to the HeaderView to
indicate whether the interface is been shown in a compact or a regular
space, and then use this value inside the view to select the appropriate
height.
The HeaderView and BodyView views are flexible, so the space available is
distributed between the two, but we limit the height of the HeaderView view
to 150 when it is presented in a vertical layout, so this view will only be 150
points tall in a vertical layout but extend from top to bottom in a horizontal
layout.
Figure 6-40: Different layouts for Compact and Regular size classes

The GeometryReader view calculates its position and size and sends this
information to the closure that defines the content. The values are stored
in an instance of the GeometryProxy structure. This structure provides the
following properties and method to get the values.
size—This property returns a CGSize value with the width and height
of the GeometryReader view.
safeAreaInsets—This property returns an EdgeInsets value with the
insets of the safe area.
frame(in: CoordinateSpace)—This method returns a CGRect value
with the position and size of the GeometryReader view. The values are
returned according to the coordinate space specified by the in
argument. The argument is an enumeration with the values global, local,
and named(String).
The GeometryReader view works like a flexible view. It stretches to occupy all
the space available and sends a GeometryProxy value with its position and
size to the closure. Using these values, we can adapt the views or perform
any change on the content we want, as shown below.
HStack {
Text(message)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment:
.center)
}.ignoresSafeArea()
}
}

Besides the size, we can also get the position of the GeometryReader view.
This information is returned by the frame() method of the GeometryProxy
structure and the values depend on the selected coordinate space. A
SwiftUI interface defines two types of coordinates spaces, one called global
that represents the coordinates of the screen, and another called local that
represents the coordinates of the view (every view has its own coordinate
space). If we read the values considering the local coordinate space, the
position is 0,0 (0 horizontal points and 0 vertical points), because the
coordinate space of the GeometryReader view always starts at the position
0,0, but if we select the global coordinate space, the values returned by the
method represent the position of the view relative to the screen. The
following example implements this method to show how to work with
these values.
The GeometryReader view is a flexible view and therefore it takes all the space
available in its container. This means that we can use it to calculate the size
and position of any view by presenting the GeometryReader view as a
secondary view. Secondary views are those assigned to other views, like
the ones assigned to the view's background. For instance, we can add a
background to an image and embed the background view in a
GeometryReader view to determine the size of the image.
Listing 6-63: Reading the position and size of a view

struct ContentView: View {
@State private var size: CGSize = .zero

Do It Yourself: Update the ContentView view with the code in Listing 6-
63. Run the application on the iPhone simulator. You should see the
size of the Image view at the bottom, as shown in Figure 6-44.
The GeometryReader view sends the information down the hierarchy. Only the
views within the GeometryReader view can read and use these values. If we
need to send the values up the hierarchy, we must use Preferences.
Despite what the name suggests, Preferences are just named values that
we can generate from a view and read from the rest of the views in the
hierarchy. For instance, if we have a Text view inside a VStack view, the
preference values generated by the Text view are accessible from the VStack
view. The values are stored in what is called a Preference Key. This is a
structure that conforms to the PreferenceKey protocol. The protocol has the
following requirements.
The process to pass a value from one view to another begins by defining a
structure that conforms to the PreferenceKey protocol. Then, we must apply
the preference() modifier to a view with the value we want to send. And
finally, we must apply the onPreferenceChange() modifier to a view in the
hierarchy (a parent view or a container of the previous view) to process the
value. The following example illustrates how the process works. We
implement a GeometryReader view to determine the size of an image, as we
did in the previous example, but send the values to the rest of the views
using a PreferenceKey structure.
The first thing we do in Listing 6-64 is to define the structure to store the
values. To conform to the PreferenceKey protocol, this structure must meet
some requirements. First, we must define a typealias called Value. Because
we are going to store the CGSize value returned by the size property of the
GeometryProxy structure, we define Value as a typealias of CGSize. Next comes
a type property called defaultValue. This property defines the value of the
structure by default. Since we are working with a CGSize structure, we
define it as a CGSize structure with its values set to 0 (zero). Finally, we
implement the reduce() method. This method receives a reference to the
values already stored in the structure and a closure that returns the new
value. To store this new value, we must execute the closure and add the
value returned to the values already stored in the structure. In this case,
we are working only with one value (a CGSize structure), so we assign it
directly to the property (value = nextValue()).
In the view, we embed an Image view with a VStack view and apply a
GeometryReader view to the background, as done before, but this time we
store the values in an instance of our BoxPreference structure with the
preference() modifier. Now, this value is defined as a Preference and therefore
we can read it from the views in the hierarchy with the onPreferenceChange()
modifier. In this example, the modifier is applied to the VStack view. When a
new value is received, we assign it to a @State property to update the view.
As a result, the view always shows the size of the image on the screen,
even when the device is rotated.
The simplest tool provided by SwiftUI to create a list of views is the ForEach
view. This view generates a loop that iterates through the values of a
collection and creates a new view for each one of them. The structure
includes the following initializer.
A ForEach view needs two values: the collection from which is going to get
the data to build the list of views, and a key path that determines the value
used to identify the views. This is important because the system needs to
identify the views to remove them or add new ones when the collection is
updated. If the collection is made of standard Swift values like Int or String,
assigning an identifier is easy. These data types conform to the Hashable
protocol and therefore they have a hash value that identifies each instance
(see Listing 3-177). To use this hash value as the identifier, we must specify
the \.self key path, as in the following example.
The ForEach view adds the content to its container, so we must declare it
inside a container view like a VStack or an HStack, as we did in Listing 7-1. In
this example, the loop is defined with a range of integers from 1 to 5, and
the views are identified by the hash value (\.self). The ForEach view loops
through the values in the range and sends them to the closure one by one.
The closure receives each value and then creates a Text view with it to show
it on the screen.
In this example, the values are presented on the screen with a VStack and
the configuration by default (center alignment and a width determined by
the widest view). Although we can use all the views at our disposal to
present each value and apply any styles we want, there is a standard
design that users immediately recognize in which the values are separated
by a line. SwiftUI includes the following view to create this line.
The Divider view is like any other view. If we want to position the line
generated by this view below the value, we must embed the Text view and
the Divider view in a VStack.
The ForEach view in Listing 7-2 creates a list of views from an array of
strings. The structure defines the array with the name of three cities and
then implements the ForEach view to create the list. Notice that String values
are hashable, so we can also identify them by the hash value with \.self.
Values of primitive data types like integers and strings conform to the
Hashable protocol and therefore we can use their hash value to identify the
views, as we did in these examples. But when working with data stored by
the user in our model, it is better to provide a unique identifier that is
stored along with the data and therefore is always the same. For this
purpose, SwiftUI offers the identifiable protocol. The protocol requires the
structure to define a property called id to store a unique identifier for each
instance, and this is how we prepare the values in our model to work with
list of views. For example, the following model creates a structure to store
information about a book, and another to work as the view model for our
application that includes an id property with the unique identifier for each
book.
init() {
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
year: 2011, selected: false)),
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover:
"book2", year: 2017, selected: false)),
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)),
BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W.
Kernighan", cover: "book4", year: 1988, selected: false)),
BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover:
"book5", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)),
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover:
"book7", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer",
cover: "book8", year: 1982, selected: false)),
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover:
"book9", year: 1993, selected: false)),
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
1983, selected: false)),
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
selected: false)),
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
"book12", year: 2007, selected: false))
]
}
}

The Book structure is the actual model. It includes properties to store the
title of the book, the author, the name of the image for the cover, the year
the book was published, and a Boolean property to indicate whether the
book is currently selected or not. On the other hand, the BookViewModel
structure defines the view-model. It includes a property to store all the
information about the book (book), computed properties to prepare the
information for the views, and the id property required by the protocol to
store the value that identifies each book. In this case, we have decided to
use a UUID value. This is a structure defined by the Foundation framework
that guarantees that every time it is created it returns a unique value, so it
is perfect to identify the books.
The observable object is defined with a property called userData to store an
array of BookViewModel structures with the information of every book stored
by the user. And finally, the array is initialized with a total of 12 books to
test the application.
Once we have the model, we need to decide how are we going to access it
from the views. For small applications, we can store it in a @StateObject
property and pass it to the views one by one, as we did in Chapter 6 (see
Listing 6-14), but as we will see in Chapter 8, professional applications are
composed of multiple views, so a better approach is to assign the instance
to the environment and then access it from the views with the
@EnvironmentObject property wrapper, as we also did in Chapter 6 (see
Listings 6-20 and 6-21). The following are the changes we need to
introduce to the App structure to inject our model into the environment.
When the data conforms to the identifiable protocol, the system knows how
to identify the items, so all we need to do is to declare the collection, and
the list of views is automatically created from the values.
Listing 7-5: Creating a list of views with data from the model

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
The ForEach view reads the array with all the books from the userData
property of our observable object and sends the items one by one to the
closure. The items are instances of the BookViewModel structure, each with
the information of a book, so we read the structure's properties to show
the information on the screen.
Figure 7-3: List of views created from the data in the model
The ApplicationData class defined in Listing 7-3 stores 12 books in the model
for testing purposes. This generates a long list of books that extends
beyond the screen boundaries and therefore the user is not be able to see
it. For a list to effectively show all the content available in the model, it
must be able to scroll. To convert a static list of views into a scrollable one,
SwiftUI includes the ScrollView view. The following is the structure's
initializer.
The ScrollView structure just creates a scrollable view, but the content is still
generated by a vertical or a horizontal stack. SwiftUI defines the following
views for this purpose.
Lazy stacks are similar to the normal stacks created by the VStack and HStack
views, with the difference that the views in a lazy stack are created as
needed. The system takes care of creating the views only before they are
about to be rendered onscreen, consuming less resources and improving
performance. Other than the use of lazy stacks, the list is defined as
before, but embedded in a ScrollView view.
By default, the axis of the view is set to vertical, the indicators are shown,
and the content adapts to the size of the view, so all we need to do in our
example is to replace the VStack views implemented before with a
LazyVStack and embed the list in a SrollView view. Now the user can scroll the
list of books.
IMPORTANT: The lazy vertical stack is used to create each row on the
list, but the views inside a row are organized with normal stacks. This
is because we need the system to create the rows only when they
are needed, but the views that define the content of each row are
always required.
The user can scroll the list vertically or horizontally, depending on the
view's orientation, but we can also do it from code. SwiftUI includes the
following view for this purpose.
This ContentView view replaces the same view from the previous example. It
creates a horizontal scrollable list of books, but this time the ScrollView view
is embedded in a ScrollViewReader view so we can control it from code. To be
able to scroll to any row on the list, we assign the id() modifier to all the
CellView views created by the loop and use the value of the book's id
property as the view's identifier. Below the ForEach view, we include a Button
view to let the user scroll the list to the beginning. For this purpose, we get
the first item in the userData array, read the id property, and call the scrollTo()
method on the proxy with this value. As a result, when the user scrolls the
list to the end, a button appears, and if pressed, it scrolls the list back to
the beginning.
The LazyVGrid and LazyHGrid views create a grid of views arranged in rows
and columns, but the size and quantity of views in a row or a column is
determined by instances of the GridItem structure.
Griditem(Size, spacing: CGFloat?, alignment: Alignment?)—
This structure defines the size, padding, and alignment of each item
on the grid. The first argument is an enumeration that includes three
associated values: adaptive(minimum: CGFloat, maximum: CGFloat),
fixed(CGFloat), and flexible(minimum: CGFloat, maximum: CGFloat). The spacing
argument defines the space around the item. And the alignment
argument defines the item's alignment. It is a structure with the type
properties bottom, bottomLeading, bottomTrailing, center, leading, top,
topLeading, topTrailing, and trailing.
let guides = [
GridItem(.fixed(75)),
GridItem(.fixed(75)),
GridItem(.fixed(75))
]
var body: some View {
ScrollView {
LazyVGrid(columns: guides) {
ForEach(appData.userData) { book in
Image(book.cover)
.resizable()
.scaledToFit()
}
}
}.padding()
}
}

In the previous example, the views are always 75 points wide. If what we
want is to create three items per row but expand the items to occupy all
the space available, we can turn the items flexible with the flexible() value,
as shown next.
The flexible() value still represents one single item, so we still must include
three GridItem structures in the array to include three items per row in our
grid, but because we are not declaring a fixed size, the items expands to
occupy all the space available in the container, as shown below. (Notice
that we have also align the items to the top with the alignment argument.)
The flexible() value represents a single item, so the number of items per row
is always the same. If what we want is to display as many items as possible
per row but keeping the same proportions and space between views, we
can use the adaptive() value.
Like the ForEach view, the List view in Listing 7-13 reads the values in the
model and creates the rows with the views defined by the closure. In this
example, we use a custom view to create the rows called CellBook. This view
includes an HStack with all the same views we used before to display the
book's information.
Figure 7-10: List of rows created by a List view
In mobile devices, the list is shown with a style that generates a padding
around the views and includes a background color. The View protocol
defines the following modifier to customize the style.
The style set by default in mobile devices is called insetGrouped, but we can
change it with the listStyle() modifier. The plain style displays the views with
not background or padding, the inset style adds padding to the list, the
insetGrouped style adds a background and a padding around the views, and
the sidebar style adds a background to the whole list. The following example
applies a plain styling to the list.
The result is similar to what we achieved with the ForEach and Divider views
before (see Listing 7-2), but now the list has an inset on the left side that
gives it a distinctive look and is able to manage large number of items.
The list styles provide a standard configuration. The View protocol includes
the following modifiers for customization.
These modifiers are applied to the rows, but by modifying the rows we can
change the style of the entire list. For instance, we can remove the padding
assigned to the rows by the plain style with the listRowInsets() modifier.
In this example, we remove the insets on the sides, but assign an inset of
10 points to the bottom of each row. To show how other modifiers work,
we have also assigned a background color and removed the separators.
The result is shown below.
The view in Listing 7-16 defines a property called colors with the two colors
we want to assign to the views (white and gray). In the closure assigned to
the List view, we get the index of the book with the firstIndex() method. The
method looks for an item in the array which id property has the same value
than the id property of the current book, and returns the index of the item
if found, or nil otherwise. To make sure that we always get a value, we use
the nil-coalescing operator (??) to return the index 0 if no value is found.
The listRowBackground() applies the remainder operator to the index value to
select the color (see Chapter 2, Listing 2-49). If the value is even, the first
color is assigned to the background (white), and if the index is odd, the
second value is assigned instead (gray).
The List view was designed to work along with the ForEach view to mix static
and dynamic content. The advantage is that we can create a list of rows
with the data from the model and at the same time include other rows
with static content to incorporate additional information or for styling
purposes, as in the following example.
Listing 7-17: Mixing static and dynamic content in a list

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
A List view takes a list of views, one or more ForEach loops, or a combination
of both, and includes every view in a row. In Listing 7-17, we create one
static view with an SF Symbol and some text, and then a ForEach view to list
the data in the model. The static content is shown along with the dynamic
content and even scrolls with the rest of the rows, as shown below.

Sections

A list may be organized in sections. Sections help the user identify common
values. SwiftUI includes the Section view to create these sections.
The text assigned to the section's header is displayed at the top of the
section with a style that matches the style of the list, but we can make it
more prominent with the following modifier.
This example creates two sections, one to show the total number of books
in the model and another to list the books.
These modifiers work similarly to the same modifiers for rows. We can
remove the top and bottom lines, or both, and change their colors. For
instance, in the following view we remove the top line and change the
color of the bottom line for the first section, and completely remove the
lines for the second section.
IMPORTANT: If you scroll down the list, you will see that the views
go all to the top, behind the status bar. This is because List views
were designed to work with the navigation bars provided by
NavigationStack views. We will learn how to use these views and
navigate between views in the next chapter.
So far, we have used sections to group different types of content. The first
section of our example contains information about the model and the
second section contains the list of books. But sections also come in handy
when we need to separate the content in groups, like movies in categories,
or organize items alphabetically. For this purpose, we must prepare the
data according to the organization we want to present to the user. An
alternative is to define a computed property in the view that returns the
list of books in alphabetical order.
Before defining the body property, the view in Listing 7-21 defines a
computed property that returns an array of tuples with the books
organized by the title's first letter. First, the closure applies the
Dictionary(grouping:, by:) initializer to create a dictionary from the values of the
userData array (the list of books in the model). The way this initializer works
is that for every value in the array, it executes a closure and then groups
the results by the values returned by the closure (see Listing 3-122). In this
example, we get the index of the first character in the book's title with the
startIndex property. Then, we use that index to extract the first character.
And finally, we turn it into a string and return it. This creates an array with
tuples which first value is a letter of the alphabet and the second value is
an array with all the books which titles begin with that letter ([(key: String,
value: [BookViewModel])]). After this array is created, we sort the values
alphabetically with the sorted() method and return it, so every time the
views read the orderList property, they get an array of tuples with the books
sorted alphabetically.
The values of the tuples were identified with the labels key and value, so we
can use these labels to read them and create our list. In this example, we
define a List view with two ForEach loops inside, one to create the sections
for each letter and another to list the books inside the sections. The first
ForEach loop reads the content of the orderList property and creates a
section with the value identified by the key label (the letter). Inside the
section, another ForEach loop iterates through the array identified with the
label to create the list of books. As a result, we get a list of books
value
organized alphabetically into sections.

Edit Mode

Listviews also provide tools to work with the values on the list. The
following are the modifiers available to remove and sort views.
The onDelete() and onMove() modifiers identify the items by the indexes in the
array. The closure assigned to these modifiers receives a set with the
indexes of the items to be deleted or moved, and all we need to do is to
call the remove() or move() methods with these values.
There are different ways to allow the user to modify the list. For instance, if
we apply the onDelete() modifier to the list, the system automatically
activates a feature that lets the user drag the rows to the left to expose a
Delete button. When this button is pressed, the closure assigned to the
modifier is executed and we can proceed accordingly, as shown next.
Notice that the modifiers are implemented by the ForEach view, so we need
to create the list with this view. In the example of Listing 7-22, we use the
onDelete() modifier. The closure assigned to the modifier receives an IndexSet
value with the index of the row to be deleted and calls the remove() method
on the userData array to remove the item from the model. Now the user can
delete the book by dragging the row to the left and pressing the Delete
button.
Besides the possibility to drag a row to delete it, List views also include a
set of tools that allow the user to select, remove, and move rows. The tools
are displayed to the user when the list is in edit mode. The easiest way to
activate this mode is with the EditButton view. This view creates a button
that activates and deactivates the edit mode when pressed, as shown next.
The view in Listing 7-23 embeds the List view in a VStack view to add an
EditButton at the top. When the button is pressed, the system activates the
edit mode and the tools are shown according to the modifiers applied. For
instance, in our example, we included the onDelete() modifier, so the view
exposes buttons to let the user delete the row.
The tool to move rows is included when the onMove() modifier is applied to
the ForEach view, as in the following example.
The system detects that we have implemented the onMove() modifier and
automatically provides the tools for the user to move the rows and
reorganize the list. After the user drops a row in a new position, the
modifier sends the indexes of the rows and the new location to the
closure, and here is where we have the chance to call the move() method to
perform the change in the userData array, so the next time the list is
redrawn, the rows remain in the position selected by the user.
List(selection: $selectedRow) {
ForEach(appData.userData) { book in
CellBook(book: book)
}
}.listStyle(.plain)
}
}
func removeSelected() {
if let index = appData.userData.firstIndex(where: { $0.id == selectedRow }) {
appData.userData.remove(at: index)
selectedRow = nil
}
}
}

When working with structures that conform to the Identifier protocol, the
List view automatically identifies the views with the values assigned to the
id property. In our example, these are the UUID values assigned to each
book. When a row is selected, the List view assigns this identifier to the
@State property, so we must declared that property with the same data
type. But in this example we use a different approach. The Identifier protocol
defines a typealias for the property's data type called ID (a typealias is an
alternative name for a data type). By declaring a @State property of type
BookViewModel.ID we make sure that the data type assigned to the property
is the same assigned to the id property, no matter if later we replace this
identifier with another one.
If the user selects an item, the List view assigns the value of the item's id
property to the selectedRow property, so we can do whatever we want with
it. In this case, we add a Button view at the top that calls a method to
remove the selected book. The method gets the index of the selected item
in the userData array, calls the remove() method to remove it, and assigns the
value nil to the selectedRow property to cancel the selection. Notice that we
have applied the disabled() modifier to the button to only enable it when a
book has been selected.
The selection argument supports the selection of one or more items, but
for multiple selection to be enabled we must activate the edit mode, as we
did before (unless a keyboard is present). To process multiple values, we
can store the identifiers in an IndexSet structure and apply the same
methods used above to move and remove rows. The IndexSet structure
provides the following method for this purpose.
List(selection: $selectedRows) {
ForEach(appData.userData) { book in
CellBook(book: book)
}
}.listStyle(.plain)
}
}
func removeSelected() {
var indexes = IndexSet()
for item in selectedRows {
if let index = appData.userData.firstIndex(where: { $0.id == item }) {
indexes.insert(index)
}
}
appData.userData.remove(atOffsets: indexes)
selectedRows = []
}
}

The view now includes two views at the top: the EditButton view to activate
or deactivate the edit mode, and the trash can button to remove the books
selected by the user. When the user activates the edit mode, checkboxes
appear on the left to select the rows. If the user selects a row, the List view
adds the value of the item's id property to the selectedRows property, so we
can call our removeSelected() method again to remove the books. In this case,
the method creates an empty IndexSet structure and then iterates through
the values in the selectedRows property to find the index of each book and
add it to the set. Once all the indexes are found, we call the remove()
method on the userData array to erase the books, and then clean the
selectedRows property to allow the user to start the process again.

Do It Yourself: Update the ContentView view with the code in Listing 7-
26. Press the Edit button. Select a row and click the button on the
right. The row should be removed, as shown in Figure 7-23.
If we want to deactivate the edit mode after books are removed, or after
any other action for that matter, we must control the mode
programatically. The environment offers the following property for this
purpose.
editMode—This property defines the state of the edit mode for the
view. It is a Binding property of type EditMode, an enumeration with the
values active, inactive, and transient. The enumeration also includes the
isEditing property to return a Boolean value that indicates whether the
view is in edit mode or not.
To manage the mode, we need a @State property with a Boolean value that
stores the state of the edit mode (active or inactive), we must set the
mode according to the value of this property with the environment() modifier,
as we did before for other environment properties, and we also need to
create a button to toggle this value and change the mode.
List(selection: $selectedRows) {
ForEach(appData.userData) { book in
CellBook(book: book)
}
}.listStyle(.plain)
.environment(\.editMode, .constant(editActive ? EditMode.active : EditMode.inactive))
}
}
func removeSelected() {
var indexes = IndexSet()
for item in selectedRows {
if let index = appData.userData.firstIndex(where: { $0.id == item }) {
indexes.insert(index)
}
}
appData.userData.remove(atOffsets: indexes)
selectedRows = []
editActive = false
}
}

This code defines a @State property called editActive to keep track of the edit
mode and replaces the EditButton view with a regular Button view. If the
value of the property is true, it means that the mode is active, otherwise
inactive, so we use it to select the mode, to deactivate it after the selected
books are deleted, and to set the title for the button ("Done" if true, "Edit"
if false). Notice that the environment's editMode property is a Binding property
and therefore we had to use the constant() method to provide a Binding value
of type EditMode (see Listing 6-11).
The result is the same as before, but the process is now customized and
therefore we can change the edit mode from code anytime we want by
assigning the value true or false to the editActive property.
Do It Yourself: Update the ContentView view with the code in Listing 7-
27. Press the Edit button. Select a row and click the Remove button.
The row should be removed, as before, but now the edit mode is
automatically deactivated.
In this example, we allow the selection of one row at a time. If the user
taps on a row, the onTapGesture() modifier checks whether the book's id was
already stored in the @State property. If it was, it deselects the row by
assigning the value nil to the property, otherwise, the value of the book's id
property is assigned to the @State property to indicate that the row was
selected. To show the selection to the user, we connect the @State property
with a @Binding property in the CellBook view and then show an image if the
value of this property is equal to the book's identifier. As a result, a
checkmark is shown on the row when it is selected.
Although we can access and modify the Book structures in the model using
the array index, the List and ForEach views can take a Binding value, which
allows us to modify the model directly. In this example, we toggle the value
of the selected property every time the user taps on a row. If the value is true,
it becomes false, and vice versa. Notice that we modify the value of the
selected property from the view-model. This is possible because we have
declared a setter for this property that sets the value of the Book structure
for us (see Listing 7-3).
The result is the same as before, but now we can select multiple books and
each Book structure knows whether the book is currently selected or not.
Swipe Actions

The Delete button displayed by the list when the user swipes a row to the
left is called Swipe Action. The swipe action that defines the Delete button
is provided by the List view, but we can define our own with the following
modifier.
By default, the swipe action is created on the trailing side and it allows to
perform the first action with a full swipe, so if that configuration is good for
our application, all we need to do is to define the closure with the Button
views we want to include.
In this example, we include only one button. We assign the destructive role to
the button, so it is shown in red, and create an Image view for the label with
the SF Symbol of a trash can. If the user swipes the row and presses the
button, the closure calls a method to remove the book from the model, as
we did before.
Besides the buttons generated by the system, we can include our own. Of
course, when we use custom buttons to perform tasks on the list, there is
no need to implement the methods defined for the edit mode. The
following example illustrates how to include a remove button in each row.
Button(action: {
removeBook(book: book)
}, label: {
Image(systemName: "trash")
.foregroundColor(.red)
.frame(width: 30, height: 30)
}).padding(.top, 5)
.buttonStyle(.plain)
}
}
func removeBook(book: BookViewModel) {
if let index = appData.userData.firstIndex(where: { $0.id == book.id }) {
appData.userData.remove(at: index)
}
}
}

The buttons created by default by the Button view pass to the row the
responsibility of responding to the user tapping the screen. To get the
button to respond, we must define it as a plain button with the plain style.
The action and the label are defined as always. In this example, we create a
button on the right hand side of each row to delete it. The label is an SF
Symbol of a trash can, and the action calls the removeBook() method
implemented before.

Refreshable

To add this feature to the list, we must implement the modifier on the List
view and provide a closure with the task we want to perform, as in the
following example.
In addition to the list of views we have created so far, SwiftUI also provide
tools to create hierarchical lists. These are list of views in which some rows
can expand or collapse to display or hide other rows. The main views, also
called parents, work as containers for other views, called children, that the
user can see by tapping on the top view's accessory. The List view provides
the following initializer to create these lists.
We don't need anything new to create these types of lists, all the
functionality is provided by the List view, but the information in the model
must be organized accordingly. For instance, the following model includes a
structure to store all the items, but the structure includes a property with
an array of instances of the same structure to store the items that are
going to be shown when the parent item is expanded.
Listing 7-33: Defining the model and the view to create a hierarchical list

import SwiftUI
init() {
items = [
MainItems(name: "Food", options: [
MainItems(name: "Oatmeal", options: nil),
MainItems(name: "Bagels", options: nil),
MainItems(name: "Brownies", options: nil),
MainItems(name: "Cheese", options: [
MainItems(name: "Roquefort", options: nil),
MainItems(name: "Mozzarella", options: nil),
MainItems(name: "Cheddar", options: nil)
]),
MainItems(name: "Cookies", options: nil),
MainItems(name: "Donuts", options: nil)
]),
MainItems(name: "Beverages", options: [
MainItems(name: "Coffee", options: nil),
MainItems(name: "Juice", options: nil),
MainItems(name: "Lemonade", options: nil)
])
]
}
}
struct ContentView: View {
@ObservedObject var contentData = ContentViewData()
The MainItems structure includes the name property to store the item's name,
and the options property to store the children (also defined by MainItems
structures). In this example, we define two parent items called "Food" and
"Beverages", each with their own children assigned to the options property.
There is one item called "Cheese" with three more children, creating an
additional hierarchy. When the user taps on the "Food" or "Beverages"
items, all the items stored in their options property are displayed, and the
same happens whit the "Cheese" item.
For instance, we can include this view inside a Section view to divide the
hierarchy in sections. We can even make the sections expandable with a
sidebar style list, as shown next.
In this example, we create the list with a ForEach view and the values of the
items property. This property contains two items, "Food" and "Beverages",
that we use to create the sections with a Section view. To display the content
of the sections, we use an OutlineGroup view. This view takes the values in
the options property of each item and creates an outline list. This creates a
list with two sections and the children views inside, but because we
declared the style for the list as sidebar, the sections include a disclosure
accessory and become expandable.
The columns are defined by the TableColumn view. The following is the view's
initializer.
By default, the columns are flexible, which means that the width is
determined by the number of columns in the table and the space available,
but the TableColumn structure includes the following modifiers to specify a
custom width.
width(CGFloat?)—This modifier defines a fixed width for the
column.
width(min: CGFloat?, ideal: CGFloat?, max: CGFloat?)—This
modifier defines a flexible column but with constraints.
As always, we need a model with some data to test the app. The following
includes a structure with a few properties to provide the values for the
columns, a @Published property to provide the values to the views, and
some data for testing.
init() {
listOfItems = [
ConsumableItem(name: "Bagels", category: "Baked", calories: 250, included: false),
ConsumableItem(name: "Brownies", category: "Baked", calories: 466, included: false),
ConsumableItem(name: "Butter", category: "Dairy", calories: 717, included: false),
ConsumableItem(name: "Cheese", category: "Dairy", calories: 402, included: false),
ConsumableItem(name: "Juice", category: "Beverages", calories: 23, included: false),
ConsumableItem(name: "Lemonade", category: "Beverages", calories: 40, included: false)
]
}
}

The Table structure includes multiple initializers, each one with a different
combination of arguments, so we can only implement those we need.
Something similar happens with the TableColumn view. For instance, if the
value presented by the column is a string, we can just specify the key path
and the view takes cares of creating the Text view to show the value. If not,
we must provide the closure to the content argument, format the value,
and create the view we want to use to show it.
The following example shows a possible implementation. The Table view is
defined only with the data source (the listOfItems property) and a closure to
create the columns. Some columns use a key path to provide the values
and the last one a closure with a Text view.
In this example, the first and second columns access the values of the name
and category properties of the ConsumableItem structure with key paths, but
we use a closure for the calories property because its value is an integer that
we must convert into a string.
This example assigns a width of 100 points to the Calories column with the
width() modifier and keeps the rest of the columns flexible. On iPads and
Macs, the system creates the Calories column with a size of 100 points and
distributes the rest of the space between the first two columns, while on
iPhones only the first column is displayed.
Figure 7-30: Table in iPads and iPhones
On iPads and Macs, tables include a feature that allows the user to sort the
items by tapping or clicking on the column's title. For instance, if we tap on
the header of the Name column in our example, the items will be sorted by
name. The feature is added to the table when we include the sortOrder
argument in the initializer. The argument takes a Binding property with an
array of values that determine the properties which values we want to
sort. Foundation defines the KeyPathComparator structure for this purpose.
Tables also allow the user to select one or multiple rows. The process is the
same used before for List views. We must provide a @State property to store
the identifiers of the items selected by the user and assign that property to
the table. For single selection, everything works out of the box, but iPads
without a keyboard require the edit mode to be enabled. In the following
example, we include an EditButton view to allow the user to enable this
mode and also a Text view below to show the names of the items selected
by the user.
If we want to select just one item, we can tap on it, but selecting multiple
items is only available if we have a keyboard or press the button created by
the EditButton view. To show the names of the selected items, we created a
method that maps the identifiers, gets the items from the listOfItems array,
returns the value of the name property, and then sorts and joins the values
to create a string with all the names.
In this example, we display the selected names on the screen, but of
course we can add buttons to the interface to process the values as we
need. A useful tool provided by tables for this purpose are context menus.
With context menus, the user can right-click a selected row or rows and
perform an action. SwiftUI includes the following modifier to create the
menu.
The closure assigned to the menu argument receives a copy of the set with
the values selected by the user. By checking these values, we can configure
the menu with options for when the user right-clicks on a single row, on
multiple selected rows, or the table's empty area. All we need to do to
configure the menu is to count how many items are in the array, as shown
next.
If there are no items in the set, which means that the user right-clicked on
an empty area of the table, we show a button to add a new item to the
model. If there is one item, we offer a button to remove the value from the
model, and if there are multiple items in the set, the option removes them
all. The context menu is created by the same contextMenu() modifier, but it
adapts based on where the user right-clicks (taps and holds on iPads).
In addition to Text views, tables can show other views, from images to
control views. For instance, we can include a Toggle view, which is presented
as a checkbox on the Mac, to allow the user to check or uncheck the items.
The Toggle view needs a bidirectional Binding value to read and store the
current value. The easiest way to provide this value is with a method. We
call the method with the item's id, use this identifier to get the index of the
item in the array, and return the item as a Binding value of type
ConsumableItem. This creates the bidirectional connection between the Toggle
view and the ConsumableItem structure, so every time the user clicks on the
switch or the checkbox, the new value is stored directly into the item's
included property.

The KeyPathComparator structure implemented before to sort the values
conforms to the SortComparator protocol. This protocol defines the tools
required to sort the values. The sort comparator implemented by this
structure can compare and sort values of common data types like String and
Int values, but it cannot sort custom data types or special values like
Booleans. For instance, if we want to sort the Included column from our
previous example, we must define our own SortComparator structure and
implement the following protocol requirements.
This initializer is applied as before. The only difference is that now we must
include an instance of our CompareBool structure to tell the table how to sort
the values.
A Picker view needs a Binding property to store the value currently selected
by the user and a list of views to present the values available. The list of
views can be created manually, one view per line, or dynamically with a
ForEach view, as in the following example.
The value of the Binding property is used to store the value selected by the
user and also to initialize the picker. If the property contains a value, and
the value is available in the model, the picker is shown with that value
selected. There are several ways to provide this initial value. For instance,
we can assign it to the @State property when the property is declared, or
we can select it later when the views appear with the onAppear() modifier, as
shown next.
In this example, we initialize the selectedValue property with the second value
in the listCities array and therefore this is the initial value selected by the
Picker view.
The picker identifies the items by their ids, and therefore the items on the
list are identified with the same value shown to the user (in our example,
the name of the cities). If this is not what we want, we can provide a
unique identifier for each item with the tag() modifier. For instance, we can
use the indexes of the array instead of the values. When the user selects a
value, we can use the selected index to get the name of the city and
display it on the screen. For this purpose, we can take advantage of the
indices property provided by the Swift Standard Library to return a Range
value with the indexes of a collection.
Because we are now working with the indexes of the array instead of the
values, we define the selectedValue property of type Int. To get the index of
each value, we define the ForEach view with the Range returned by the indices
property of the listCities array, and then assign the index to each value with
the tag() modifier. When a value is selected, the picker assigns the value of
the tag() modifier to the selectedValue property, and therefore we can use this
property to get the name of the city from the array.
The Picker view adopts a design according to the device and the conditions
in which it is presented, but we can force the picker to adopt the design we
want with the following modifier.
The automatic style allows the picker to adapt the design to the device and
conditions, and the menu style create a context menu, as we have seen in
the previous examples. But there are two additional styles. The wheel style
displays the values in a virtual wheel the user can rotate to make a pick,
and the segmented style defines a unique picker where the values are turned
into buttons. The latter is frequently used to allow the user to select a
value from a limited set of options, as illustrated by the following example.
The @State property used to store the selected value was initialized with the
current date (Date()) and therefore the picker shows this as the selected
date and time, but we can initialize this value with any date we want. The
picker is presented with the style by default, which in mobile devices
shows buttons to open a calendar to select the date and a spinning wheel
to select the time.
By default, the DatePicker view presents a list with all the dates from a
distant date in the past to a distant date in the future. To limit the list of
values the user can select from, we must provide a range of dates. The
range can be closed (from one date to another), or open. The following
example shows a list of dates from the current date to a distant date in the
future using an open range.
In this example, we limit the picker with the range Date()... and therefore the
user is not allowed to select a date before the current date. A range ...Date()
does the opposite, it only allows the user to select a date from the past.
But we can also provide specific dates by creating custom Date structures,
as we did in Chapter 4 (see Listing 4-23).
A DatePicker view adopts a design according to the device and the conditions
in which it is presented, but it includes the following modifier to specify
the style we want.
By default, the DatePicker view is configured with the compact style, which
shows a button the user must press to reveal a calendar to select a date or
a spinning wheel to select a time. But we can also show the calendar right
away with the graphical style, or a spinning wheel with the wheel style, as we
do in the following example.
In addition to assigning the wheel style to the picker, the view in Listing 7-
49 also includes a Text view to show the day and month selected by the
user. The result is shown below.
To allow the user to select multiple dates, we must create the picker with a
MultiDatePicker view and a Binding property that stores a set of DateComponents
values.
A MultiDatePicker view works like the DatePicker view, but the selected values
are stored in an array. In this example, we apply the onChange() modifier to
show the days selected by the user in a Text view at the bottom of the
screen.
Figure 7-36: Multiple date picker
A Form view is a container that organizes the views in a list, like the List
view, but adapts the controls to the device and the platform in which the
app is running. Creating a form in SwiftUI is easy, we just have to include
the controls one after another inside the Form view, as shown next.
The Form view adds padding on the sides of the controls and present the
list with a grouped style (see Listing 7-14).
Most views allow us to specify a string that the Form view can use to create
the label for the control, but in some cases, we may need to hide the label
and specify our own, as we did for the Stepper view in the previous example.
To create custom labels, SwiftUI includes the LabeledContent view. This view
attaches a label to another view. The following is the most frequently used
initializer.
With this view, we can better organize our previous example and provide a
custom label for the Stepper view.
A Form view works like a List view, so we can separate the content in
sections to provide a more standard design.
Some forms are extensive and hard to read. SwiftUI includes the
DisclosureGroup view to expand and collapse several controls together to
make it easy for the user to insert the values and work with the form. This
is similar to the OutlineGroup view implemented before to create hierarchical
lists, but the DisclosureGroup view works with non hierarchical information,
such as the one we find in a form. The structure includes the following
initializer.
With this view, we can organize the two sections of our form in expandable
lists.
When the views are collapsed, all we see is the view's label. If we tap on
these labels or the disclosure indicator, the content is expanded and we
can interact with the controls.

CHAPTER 8 - NAVIGATION
8.1 Multiple Views

The ContentView view we have been using in the examples of previous
chapters defines the interface for the initial screen, but apps that only
require one view and a simple interface are hard to find. Due to the size of
the screen, apps for mobile devices require multiple views that replace one
another in response to the user. Professional apps are made up of several
views connected with each other in predetermined paths that users can
follow to navigate throughout the content. Figure 8-1, below, illustrates
how these views work together to expand the interface.

Navigation Stack

navigationBarTitleDisplayMode(TitleDisplayMode)—This
modifier determines the display mode for the title. The argument is
an enumeration with the values automatic (the same mode as the
previous view), inline (adapts to the size of the bar), and large (expands
the bar to show a large title).
navigationBarBackButtonHidden(Bool)—This modifier hides the
Back button that is automatically generated by the NavigationStack view
to navigate to previous views.
statusBarHidden(Bool)—This modifier hides the status bar for all
the views in the navigation hierarchy.
A NavigationStack view creates a stack of views with the views opened by the
user. Therefore, to start this navigation hierarchy we need to embed the
content of our app's initial view in a NavigationStack view, as we do in the
following example.

Do It Yourself: Create a Multiplatform project. For this application to
work, you need to create a Swift file called ApplicationData.swift
with the model in Chapter 7, Listing 7-3 and inject an instance of the
ApplicationData class into the environment and the previews, as we did
in Chapter 7, Listing 7-4. Update the ContentView view with the code in
Listing 8-1. Download the book covers from our website and add
them to the Asset Catalog. You should see an interface similar to
Figure 8-2. Scroll the list down to see how the size of the navigation
bar changes.
By default, the navigation bar shows a large title, but we can change that
behavior by assigning the inline mode instead. This is particularly useful
when the list is implemented with a style that doesn't include any
backgrounds or padding, as in the following example.
Now the title is shown in a standard size and with a predefined style, and
all the views in the hierarchy will inherit this mode.

Toolbar

Besides the title, the navigation bar can contain other elements, including
images and buttons. SwiftUI provides the following modifiers to add items
and configure the navigation bar and other toolbars.
Items can be added to the left or right side of the toolbar, and to
different toolbars, including the navigation bar generated by the
NavigationStack view, a toolbar at the bottom of the screen, and a toolbar on
top of the keyboard. For this reason, SwiftUI includes the ToolbarItem view to
define the items.
ToolbarItemGroup(placement: ToolbarItemPlacement,
content: Closure)—This view defines multiple items for a toolbar.
The placement argument is a structure that determines the place and
the toolbar where the items will be located. The structure includes
type properties to define standard configurations for every system.
The properties available are automatic, bottomBar, cancellationAction,
confirmationAction, destructiveAction, keyboard, navigation, navigationBarLeading,
navigationBarTrailing, primaryAction, secondaryAction, principal, and status. The
closure assigned to the content argument defines the views we want
to include.
The button in the previous example prints a message on the console, but of
course we can perform more meaningful tasks. For instance, we can add
buttons to the navigation bar to scroll the list of books to the top and the
bottom, as shown next.
Buttons added to the bottom toolbar are displayed at the center. That's the
reason why in our example we have defined the button inside an HStack
view, so it could be placed on the right and share the bar with other
buttons, if necessary. Notice that we have also implemented the other
version of the toolbar() modifier to hide the navigation bar.
Although we can position the buttons on the left or right side of the
navigation bar with placement values like navigationBarLeading and
navigationBarTrailing, the ToolbarItemPlacement structure also allows us to specify
the intent of the buttons and let the system decide where and how to
present them depending on the space available and the device. For
instance, the structure returned by the primaryAction property usually places
the buttons on the right, and the one returned by the secondaryAction
property tells the system that the buttons are not essential for the
operation of the application and therefore are placed inside a popup
menu, as shown in the following example.
The toolbar() modifier includes two ToolbarItemGroup items with one button
each. The purpose of the button in the first group is to add books to the
list, which is a primary function and therefore we declare it as a primary
action, but the purpose of the button included in the second group is to
sort the list and therefore we declare it as secondary. On an iPhone, the
system places both buttons on the right, but includes the second button in
a popup menu.
The navigation bar is split in three areas: leading, center, and trailing. By
default, the title is positioned at the center and the buttons on the sides,
but we can change this behavior with the toolbarRole() modifier. This is
specially useful on iPads and Mac computers, where larger screens and
windows allow us to add more elements to the bar. For instance, if we
declare the role as editor or browser, as we did in the example in Listing 8-6,
on iPads the title is shown on the leading area and the secondary buttons
at the center.
There is a built-in tool that allows the user to add or remove buttons to the
navigation bar when they are declared as secondary actions. To activate
this feature, we must declare the identifiers for the toolbar() modifier and
the items, as shown next.
In this example, we declare the bar with the "mybar" identifier and two
buttons with the identifiers "sort" and "settings". The system uses these
identifiers to store the last configuration and restore it the next time the
app is launched. On iPhones the buttons are shown in a popup menu, as
any other secondary action, but on iPads and Mac computers the system
shows the buttons at the center and an additional button with an option to
configure the bar.
When this option is selected, the system opens a windows were the user
can drag and drop the buttons to configure the bar.
Our Menu view includes three buttons. In this case, we print messages on
the console, but the buttons can perform any task we want. The view is
represented by a button with an SF Symbol, and the menu opens when the
button is pressed.
A NavigationStack view can include a search bar to allow the user to search
for values in the model. The following is the modifier we need to apply to
this view to enable that feature.
In this model, we have turned the userData property into a normal property.
This is because all we need from this property is to store the books
available, but the data is provided to the views by a new @Published
property called filteredItems. To feed this property with the books that match
the search performed by the user, we define a method we will call every
time the list needs an update (when a new search is performed or books
are added or removed).
The filterValues() method receives a string with the value inserted by the user
in the search bar. If the value is empty, we assign all the books in the
userData property to the filteredItems property to show the full list on the
screen, but if there is a value to search, we filter the books in the userData
property with the filter() method. This method receives a value and must
return true or false to determine if it is going to be included in the result. In
this case, we return true only when the book's title contains the value
inserted by the user. The resulting array is assigned to the filteredItems
property to make it available to the views. Notice that in both cases, the
arrays are sorted with the sorted() method, so the values are displayed in
alphabetical order.
After the testing data is stored in the userData property, we call the
filterValues() method with an empty array to initialize the list. Now we can
use the filteredItems property to show the books, as we do next.
This view includes a @State property to store the value inserted by the user,
and two modifiers to manage the search. The searchable() modifier asks the
system to display a search bar. For this example, we only need to provide
two values, the Binding property to store the value (searchTerm) and a Text
view with the placeholder text we want to show in the bar. This stores the
value inserted by the user, but the search is performed by the onChange()
modifier added below. This modifier executes a closure every time the
value of the searchTerm property changes. In this closure, we trim the value
to make sure there are no spaces at the beginning or the end, and then call
the filterValues() method in the model. The method filters the values, assigns
the result to the filteredItems property, and because we used this property to
define the List view, only the books that match the search are shown on the
screen.
By default, the search bar disappears when the user scrolls the list, but we
can keep it permanently on the screen by defining the display mode. This is
an additional argument included in the searchable() modifier called
placement. This argument defines the location of the bar, which depends
on the system and the organization of the interface, but it can also define
the bar's display mode. The structure used to define this value includes the
navigationBarDrawer() method for this purpose. If we specify this method with
the value always, as shown below, the bar is permanently displayed on the
screen.
Listing 8-12: Performing the search when the Return/Done key is pressed

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@State private var searchTerm: String = ""
The suggestions are provided with a list of Text views defined in a closure. If
the views are created with the same model used to create the List view,
only values that match the search are shown. The user can close the
suggestion list by pressing the Return/Done key or by selecting one of the
values. To allow selection, the framework includes the following modifier.
The Text views with the suggestions may be declared manually to create a
static list, or with a ForEach loop to create a dynamic list. In our case, we
want to show suggestions according to the value inserted by the user, so
we must create a dynamic list with a ForEach loop.
The search bar includes two buttons, one to clear the bar and another to
allow the user to cancel the search, but we can also do it programatically.
The environment includes the following values to control the process.
The searchable() modifier sends the information down the view hierarchy
through the environment, so the system can determine where to show the
search bar and when to display it. This means that the environment values
are only available within the views in the hierarchy. Therefore, to be able to
read them, we must embed the list of books in a custom view. In the
following example, we call this view SearchableView.
Another feature we can add to a search bar are scope buttons. These are
buttons placed below the bar that allow the user to specify the scope of
the search (e.g., search by title or by author). To incorporate these buttons,
SwiftUI includes the following modifier.
enum Scopes {
case title, author
}
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@State private var searchTerm: String = ""
@State private var searchScope: Scopes = .title
In this example, we define the scopes with an enumeration and two values:
title and author. The @State property is initialized with the title value, so the
searches are first performed by title. The closure assigned to the
searchScopes() modifier includes two Text views to create two scope buttons,
one to select the title scope and another to select the author scope. To
perform the search in the current scope, we send the value of the
searchScope property to the filterValues() method in the model. The method
must now check the selected scope and search the value accordingly, as
shown below.
The search bar now includes two buttons below. If the user presses the
Title button, we search by title, and if the user presses the Author button,
we search by author.
In this new method, we must check if the user has selected an author and
then filter the array by author and title. The easiest way to do it is to start
with a valid condition (valid = true) and then invalidate it if something
doesn't match. We first check if the user selected an author and compare it
with the author of the book. If they don't match, the condition becomes
invalid. Next, we do the same for the title, but only if the condition was not
already invalidated by the author. As a result, when the user selects an
author, a token with the author's name appears in the search bar and the
search is only performed on the books written by that author.

Navigation Link

There is no stack of views if we only have one. To allow the user to open
additional views, SwiftUI includes the NavigationLink view. This is a control
view that creates a button the user can press to replace the current view
with another. The following are the view's initializers.
The NavigationLink view requires a view to open and a label the user can tap
to perform the action. For instance, the following example adds a link to
the navigation bar to open a view the user can use to change the app's
settings.
Like the Button view, the NavigationLink view creates a button but with the
purpose of replacing the current view with another one. In this case, we
create the button with an SF Symbol image of a gear, and set the
destination view to be a custom view we call SettingsView.
We have worked with values from the environment before (see Listing
6-12). All we have to do is to add an @Environment property to the view and
then read or modify its values. The dismiss value is a structure that provides
access to a closure with code to dismiss the view, so all we need to do to
remove the SettingsView view is to define an @Environment property with this
value and then execute the closure when a button is pressed, as shown
next.
In the previous example, we used an Image view to declare the label for
the NavigationLink view, but we can use any view we want, including custom
views. A common practice is to declare the rows of a list as the labels of a
NavigationLink view, so when the user taps on a row, another view opens to
show additional information.
The view opened when a row is selected is usually called Detail view
because its role is to show details about the selected item. In our example,
we want to show additional information about the book selected by the
user. For this purpose, the NavigationLink view opens a new view called
DetailView and send to this view a copy of the BookViewModel structure that
represents the book. The view receives this instance and displays the
values on the screen.
When the user selects a row in the ContentView view, an instance of the
DetailView view is created and presented on the screen transitioning from
right to left, and the values of the book are shown to the user.
Although this is a valid approach, we don't have much control over the
navigation process. The system automatically includes back buttons that
we can replace with custom buttons and the dismiss value, as we did in
previous examples (see Listing 8-21), but these buttons only remove the
current view. Professional applications allow the user to navigate through
many views that comprise long navigation paths. Going back to previous
views one by one becomes tedious and time consuming. To have more
control over the navigation path, we can access the views through the
NavigationStack view. This view can keep track of all the views opened by the
user and can store references to those views in a Binding property, so by
modifying the values stored in this property we can modify the path
programmatically (add or remove views).
There are a few steps we need to follow to control the navigation path.
First, we must create the navigation links with the NavigationLink(String, value:
Value) initializer. This initializer identifies the link with the value provided by
the value argument. Once the link is identified, we must add the following
modifier to tell the system what to open.
The label for the button is again the book's cover. When the user taps
on the cover, we call the append() method on the NavigationPath structure to
add a string to the path, and this triggers the same process as before; the
navigationDestination() modifier detects that a value of type String has been
added to the path, the NavigationStack view adds the PictureView view to the
stack, and the view is presented on the screen.
8.2 Modal Views

Besides navigation links, we can expand the interface with modal views.
Modal views are normal views but are presented on top of the rest of the
views on the screen.
Sheets

These modifiers create a sheet that partially covers the interface. SwiftUI
also includes the following modifiers to create a full-screen sheet.
This view defines a @State property to control the state of the sheet and
adds a Button view to the navigation bar that assigns the value true to this
property to open the sheet when pressed. The sheet is managed by the
sheet() modifier applied to the List view. The modifier is connected to the
showSheet property, so when the value of this property is true a custom view
called AddBookView is presented on the screen. This is the view we are using
to allow the user to add new books. The view includes TextField views to
insert the book's title, author and year, and a button to store the values in
the model.
There is nothing new in this view, all we do is take the input from the user,
trim the values to remove additional space, check whether the values are
valid, and store a new book in the model. For this purpose, we create a
new BookViewModel structure with the values inserted by the user and add it
to the userData array in the model. After the process is over, we perform the
dismiss handler, the view is closed, and the new book appears at the end of
the list.
In this example, we have included a button to close the view, but there is a
built-in feature that allows the user to remove the view by dragging it
down with the finger. SwiftUI includes the following modifier to disable this
tool.
There are also modifiers to configure the sheet. We can determine the
height of the sheet and when multiple heights are available, we can
determine if the indicator provided by the system is going to be shown or
not.
We can apply this modifiers directly to the view presented by the sheet.
For instance, the following example don't allow the user to drag down the
sheet to close it.
The sheet now occupies less space on the screen, but we can even reduce
it further with the height() method, as shown next.
There is a second sheet() modifier designed to open a sheet for each of the
values in the model. It is usually implemented to open a sheet for each row
on a list. The modifier needs an optional Binding property of the same data
type of the items in our model (the data type must conform to the
identifiable protocol). If the property contains a value, a sheet is opened with
that value, and when the value nil is assigned to the property, the sheet is
closed.
In the following example, we implement this modifier to allow the user to
edit the values of the books. When a row is selected, we assign a copy of
the BookViewModel structure that represents the book to the state property,
so the sheet is opened with this value, as shown next.
Listing 8-33: Presenting a sheet for every value on the list

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@State private var showSheet: Bool = false
@State private var editItem: BookViewModel?
This view defines a @State property called editItem to store the BookViewModel
value for the sheet. To allow the user to select a row, we add the
onTapGesture() modifier to the CellBook view. We have implemented this
modifier before to perform a task when the user taps a row (see Listing 7-
28). In this case, we assign the row's BookViewModel structure to the editItem
property. When this value changes, the sheet is opened with a second
sheet() modifier added to the List view. The closure assigned to this modifier
receives a copy of the BookViewModel structure, that we send to the
AddBookView view so the user is able to edit the values.
The AddBookView view must be updated to be able to receive the book
selected by the user and show the current values in the input fields. But we
also must contemplate that the view might be opened by the user to insert
a new book. The following are the modifications we need to introduce to
the view for this purpose.
There are a few differences in this view from the one defined in Listing 8-
29. First, we added an optional property called book to receive the book. If
the user opens the view to add a new book, the value of this property will
be nil, but if the view is opened by selecting a row, the property will contain
the BookViewModel structure with the information about the selected book.
By checking this value, we can perform tasks for each situation. For
instance, at the top of the view, we added a Text view to display the texts
"Add Book" or "Edit Book" depending on the value of this property. At the
bottom, the onAppear() modifier was included to show the values of the
book on the screen. If the book property contains a book, we assign the
values to the input fields so the user knows which book is being edited.
The storeBook() method updates or adds the book to the model. We first get
the index of the selected book with the firstIndex() method. If an index is
returned, we create a new BookViewModel structure with the values inserted
by the user and the current image, otherwise, we create the structure with
an image by default and add it to the userData array.
The popover() modifier in Listing 8-35 has two arguments: the isPresented
argument to control when the popover is shown, and the arrowEdge
argument to determine the side where the arrow is placed and therefore
the position of the view relative to the anchor view. When the value of the
showPopover property is true, the modifier creates an instance of the HelpView
view and shows it on the screen.
The HelpView view is the custom view we use to define the popover's
content. From this view, we can also determine the size of the popover
with a frame() modifier, as shown next.
The view can be removed by dragging it down with the finger on iPhones
and by clicking outside the view on iPads, but we can also do it
programmatically with the dismiss value, as in this example.
Alert views are predefined modal views that can display messages and
receive input from the user. Their purpose is to deliver to the user
important information that requires immediate attention. For example, an
alert view may be used to ask confirmation from the user before deleting
data from the model. The following is the most frequently used modifier
provided by the framework to create these views.
The process to open these views is the same as with sheets. We have to
define a @State property to manage the state of the view and then apply
the alert() modifier.
The @State property for this view is called openAlert. The view includes a
TextField view and a button. When the button is pressed, the code checks
whether the user inserted a value. If not, the value true is assigned to the
openAlert property to present the alert view. The view was defined with the
title "Error", the message "Insert your name", and a Button view that allows
the user to dismiss it. Notice that the button has been assigned a role that
corresponds to the action performed (in this case, cancel) so that the system
knows its purpose and can style it appropriately.
This modifier includes three buttons. A button to cancel the action and two
more to perform other tasks. The Cancel button was defined first, but
because the role is set to cancel, the system places it at the end of the list.

Confirmation Dialog

There is not much difference between confirmation dialogs and alert views
other than the design and location. For instance, in iPhones, a confirmation
dialog is presented at the bottom of the screen. The following example
creates a confirmation dialog with three buttons. The buttons don't
perform any action but illustrate how to implement and work with these
kinds of views.
As always, we define a @State property to manage the view. When the value
true is assigned to this property by the button, the confirmationDialog()
modifier opens a view with three buttons. There is a standard button to
perform a normal operation, a destructive button to delete data, and a cancel
button to allow the user to dismiss the view.
The content of a TabView view is defined inside the closure. The views are
introduced in a list, one after another, and the tabItem() modifier is applied
to each one of them to configure the tabs, as shown next.
This TabView view includes two views, BooksView and SettingsView. For the
BooksView view, we define a tab with an SF Symbol called book.circle and the
text "Books", and for the SettingsView view we include the SF Symbol called
gear and the text "Settings".
The BooksView and SettingsView views represent two different screens and
therefore they should be stored in separate files. For this example, we are
going to define two simple views with a text in the center. The following is
the code for the BooksView, stored in the BooksView.swift file.
And for the SettingsView view, we must create a file called SettingsView.swift
and include the following code.
We can include all the views we want in a TabView view. If there is no room
in the tool bar to display all the tabs, the system adds a tab called More
that allows the user to select the tabs that are not visible.
The View protocol includes the following modifier to add a badge to the
tabs.
In TabView views, the badge() modifier is applied to the view which tab we
want to use to show the badge. For instance, we can apply it to the
Settings view to show how many issues require the user's attention.
In this case, the badge is created with the number 12, so it will always
show that value, but badges are usually defined with values from
properties or counters used to alert the user of important issues that need
attention.
The user can tap the tabs at the bottom of the screen to open a view, but
we can also do it from code. The TabView view can use a Binding property to
store a value that determines which view is currently opened. By modifying
the value of this property, we can open a view programmatically. For this to
work, we must assign the Binding property to the selection argument of the
TabView initializer and identify each view with the tag() modifier.
init() {
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
year: 2011, selected: false)),
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover:
"book2", year: 2017, selected: false)),
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)),
BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W.
Kernighan", cover: "book4", year: 1988, selected: false)),
BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover:
"book5", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)),
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover:
"book7", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer",
cover: "book8", year: 1982, selected: false)),
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover:
"book9", year: 1993, selected: false)),
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
1983, selected: false)),
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
selected: false)),
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
"book12", year: 2007, selected: false))
]
}
}

The list is created from the values in the userData array, as always, but the
rows are now designed according to the app's configuration. If the value of
the showPictures property is true, we show the book's cover, and if the value
of the showYear property is true, we show the year of publication.
To allow the user to set these values, we must provide a form in the
SettingsView view, as shown next.
This view includes two Toggle views to set the values in the model. Now the
user can decide what to show on the list. For instance, if both switches are
off, the list only includes the books' titles and authors.
In SwiftUI, a TabView view can present two configurations. The user can
select the views from the tabs at the bottom of the screen or by dragging
the views to the sides, like turning the pages of a book. To select the
configuration we want, the TabView structure includes the following
modifiers.
The page style is usually implemented when we need to provide easy access
to visual content, such as images. For instance, we can use it to allow the
user to navigate through the books covers by swiping the images to one
side or the other.
This view creates a TabView view with a list of Image views, one for each
book, but because we declare the tabViewStyle() modifier with the value page,
instead of showing the tabs at the bottom, the view allows the user to
swipe the images to the left or the right. Notice that we've also
implemented the indexViewStyle() modifier with the value always to include a
page indicator. The results is shown below.
The columns are called Sidebar, Content, and Detail, and they are
interconnected. A NavigationLink view in the Sidebar column updates the
content of the Content column, and a NavigationLink view in the Content
column updates the content of the Detail column.
The Sidebar and Content columns are removable, and are presented on the
screen depending on the number of columns and the space available. By
default, on iPads in landscape mode, only one of the removable columns is
shown. In a two-column design, the NavigationSplitView view displays the
sidebar column, while in a three-column design the content column is
shown instead. In iPads on portrait mode and large iPhones in landscape
mode, only the Detail column is displayed, and a button is provided in the
navigation bar to open the removable columns. iPhones in portrait mode
present a different configuration; the columns are displayed as in a
NavigationStack view; they replace one another and buttons are included to
navigate back.
This example includes a @State property called selectedBook to store the book
selected by the user, another for the NavigationSplitView view to store the
columns' visibility state, and the view itself with two columns. The column
on the left (Sidebar) opens the BooksView view with a list of books, and the
column on the right (Detail) opens the DetailView view with information
about the selected book, or a placeholder view if no book was selected yet.
When a book is selected, the BookViewModel structure that represents the
book is assigned to the selectedBook property, the content of the ContentView
view is recreated, and a new DetailView view opens on the right column to
present the book on the screen.
To know when a new row has been selected, we pass a reference of the
selectedBook property to the BooksView view. Therefore, every time the user
selects a book in the BooksView view, the value is stored in the selectedBook
property, and the interface is updated. For the selection to work, the
BooksView view must include a List view connected to the selectedBook
property and also create the rows with a NavigationLink view identified with
a value that matches the value stored in that property, as shown next.
The PlaceholderView view is displayed on the right column until the user
selects a book, in which case it is replaced with a DetailView view. The
DetailView view is similar to the one we have created before for an iPhone
application, but now we must consider that it may be shown on devices
with very different characteristics, such as an iPhone in portrait orientation
or an iPad in landscape. For this types of applications, Apple recommends
adapting the interface according to the size classes (see Chapter 6). For
instance, if the horizontal size class is compact, we can display the values
on a list, as we did before, otherwise, we can take advantage of the larger
screen and present the values side by side.
Selecting the right view for the device and the space available is quite
simple in SwiftUI. All we need to do is to get the view's horizontal size class
from the environment and display one view or the other, as we do in the
following example.
The views can be declared in the same file or in separate files, depending
on their complexity. In this example, we have declared two additional
views in the same file, the DetailLarge view to define the interface for a
regular size class, and the DetailSmall view for the interface of a compact size
class. When the DetailView is loaded, we check the value of the
horizontalSizeClass property in the environment and load the corresponding
view. As a result, the DetailView view shows the book's information next to
the cover on iPads and on top of the cover on iPhones.
Although we could get a book from the model and show the DetailView view
with it, it is better to assign that book to the selectedBook property instead.
This way, the item is also selected in the List view on the left. In our
example, we use the onAppear() modifier for this purpose. On iPhones in
portrait, the application works as before, but on iPads in landscape, the
first row is selected and the book is shown on the screen.
The view on the right is just one view, but we can allow the user to
navigate to other views by embedding the DetailView view in a NavigationStack
view. To control the navigation, we need to add a NavigationPath property, as
shown next.
The procedure is the same used before to control the navigation path. We
declare the NavigationPath property and then use it to initialize the
NavigationStack view, but because we are enabling navigation for the right
column, we need to pass a reference of the path to the DetailView view.
Notice that to make sure the navigation path always begins with the
DetailView view, we clear the NavigationPath structure when a book is selected
(when the value of the selectedBook property changes).
When this application is launched, the DetailView view becomes the initial
view of the navigation stack, so the rest of the navigation is managed from
this view. We can, for instance, allow the user to tap on the cover to
expand it, as we did in previous examples.
We can also add navigation to the left column (Sidebar). The problem is
that the NavigationSplitView view is configured by default to always open
navigation links on the Detail column. In a two-column layout, the
NavigationLink views in the left column will always open the destination view
in the right column, but we can change this behavior with the following
modifier.
The BooksView view now includes an item in the navigation bar defined by a
NavigationLink view and an SF Symbol that opens a view to configure the
application, but because we applied the isDetailLink() modifier with the value
false, the SettingsView view opens in the same column, as shown below.

Do It Yourself: Update the ContentView view with the code in Listing 8-
57 and the BooksView view with the code in Listing 8-58. Create a
SwiftUI View file called SettingsView.swift. Modify the SettingsView
view with a Text view to show the message "Settings View". Run the
application on the iPad simulator in landscape orientation. Press the
Settings button in the navigation bar. You should see the SettingsView
view open in the left column.
Three-Columns Layout

func updateAuthors() {
var list: [String] = []
for name in userData.map({ $0.author }) {
if !list.contains(name) {
list.append(name)
}
}
listAuthors = list.sorted(by: { $0 < $1 })
}
init() {
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
year: 2011, selected: false)),
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover:
"book2", year: 2017, selected: false)),
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)),
BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W.
Kernighan", cover: "book4", year: 1988, selected: false)),
BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover:
"book5", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)),
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover:
"book7", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer",
cover: "book8", year: 1982, selected: false)),
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover:
"book9", year: 1993, selected: false)),
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
1983, selected: false)),
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
selected: false)),
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
"book12", year: 2007, selected: false))
]
updateAuthors()
}
}

Because we now have two columns with list of values the user can choose
from, we need two properties to store the selection. We call them
selectedAuthor and selectedBook. The view in the first column, called AuthorsView,
presents the list of authors available, so we pass a reference to the
selectedAuthor property to capture the user's selection. On the other hand,
the view in the second column, called BooksView, presents the list of books
that belong to the selected author, so we need to pass a reference to the
selectedBook property to control selection and also the value of the
selectedAuthor property to filter the books by author.
The AuthorsView view must create a list of authors and allow the user to
select one. The code is similar to previous examples.
The BooksView view is also similar to previous examples, but now we only
need to display the list of books that belong to the author selected by the
user. To filter the values, we define a computed property called listBooks and
use it to feed the List view.
The listBooks property gets the author's books and returns them in
alphabetical order. The rest of the view is the same as before. We create a
List view with these values and embed the rows in a NavigationLink view to
update the selectedBook property when a book is selected.
The width of the columns, which columns will be visible, and how they are
going to be presented on the screen, is determined by the space available,
but we can suggest a specific configuration and the system will try to
comply when possible. One of the things we can do is to suggest the
number of columns we want to be visible by modifying the value of the
Binding property assigned to the columnVisibility argument. We can ask
the system to show all the columns (all), only the Content and Detail
(doubleColumn), and only the Detail column (detailOnly). For example, we can
hide the column on the left when an item is selected.
navigationSplitViewStyle(NavigationSplitViewStyle)—This
modifier defines how the Detail column is displayed when other
columns are present. The argument is a structure that conforms to
the NavigationSplitViewStyle protocol. The protocol defines the type
properties automatic (default), balanced (the size of the Detail column is
reduced to make room for the rest of the columns), and prominentDetail
(the size of the Detail column is maintain and the rest of the columns
are overlaid on top).
navigationSplitViewColumnWidth(CGFloat)—This modifier
defines a fixed width for the column.
navigationSplitViewColumnWidth(min: CGFloat?, ideal:
CGFloat, max: CGFloat?)—This modifier defines a flexible column
but with constraints.
The style is applied to the NavigationSplitView view, but the width is applied to
the columns. In the following example, we set the width for the Sidebar
and Content columns to 200 points, and apply the prominentDetail style to the
view, so the columns on the left open over the Detail column but take up
only a portion of the screen.
Listing 8-64: Configuring the columns

struct ContentView: View {
@State private var selectedAuthor: String?
@State private var selectedBook: BookViewModel?
@State private var visibility: NavigationSplitViewVisibility = .automatic
Because multiple applications can run at the same time, the system doesn't
allocate a specific number of cores per application. What it does is to
create execution threads, assign the tasks to these threads, and then
decide which threads are going to be executed by which core depending
on the available resources. In the example of Figure 9-1, left, there is an
asynchronous task that loads an image from the Web and then displays it
on the screen. While waiting for the server to respond, the thread is free to
perform other tasks, so the system may use it to execute a different task
that updates the progress bar. On the right, the tasks were created as
concurrent tasks and therefore they are executed simultaneously in
different threads.
Tasks

There are also a few type properties and methods available to get
information from the current task or create tasks that perform specific
processes. The following are the most frequently used.
Asynchronous and concurrent tasks are defined in Swift with the async and
await keywords. For instance, to create an asynchronous task, we mark a
method with async and then wait for that method to complete with await.
This means that an asynchronous method can only be called with the await
keyword from inside another asynchronous method, which creates an
indefinite cycle. To start the cycle, we can initiate the asynchronous task
when the view appears with the task() modifier, as shown next.
Listing 9-1: Initiating an asynchronous task

struct ContentView: View {
var body: some View {
VStack {
Text("Hello, world!")
.padding()
}
.task(priority: .background) {
let imageName = await loadImage(name: "image1")
print(imageName)
}
}
func loadImage(name: String) async -> String {
try? await Task.sleep(nanoseconds: 3 * 1000000000)
return "Name: \(name)"
}
}

The task in this example is created with a background priority, which means
that it is not going to have priority over other parallel tasks. In the closure,
we call the loadImage() method and then print on the console the value
returned. This is a method we define to simulate the process of
downloading an image form the Web. We will learn how to download data
and connect to the Web later, but for now we use the sleep() method to
pause the task for 3 seconds and pretend that the image is downloading
(the method takes a value in nanoseconds). Once this pause is over, the
method returns a string with the file's name. To define the method as
asynchronous, we add the async keyword after the parameters, and then
call it with the await keyword to indicate that the task must wait for this
process to be over.
The task() modifier creates the task and adds it to the thread. When the
view is loaded, the closure assigned to the modifier is executed. In the
closure, we call the loadImage() method and wait for its completion. The
method pauses for 3 seconds and then returns a string. After this, the task
continues executing the statements, and a message is printed on the
console.
The processes are executed one by one, in sequential order. The task waits
for a process to be over before executing the next. In this case, the whole
task is going to take 9 seconds to finish (3 seconds per process).
This view performs the same three processes as before, but now the task is
defined explicitly, which gives us more control over it. For instance, now we
can assign the task to a variable and then call the cancel() method to cancel
it.
The cancel() method cancels the task, but the processes are not
automatically cancelled; we must detect whether the task has been
cancelled with the isCancelled property and stop the process ourselves, as
shown next.
Because we need to wait for the task to finish before using the value, we
have defined a second task. The process starts as always, with a task that
calls the loadImage() method, but now we create a second task that returns a
string. This task executes another asynchronous method that waits for 3
seconds and returns the number 50000. After this process is over, the task
creates a string with the name and the number and returns it. We then get
the string from the value property, and return it to the original task, which
prints it on the console.
So far, we have worked with asynchronous methods, but we can also
define asynchronous properties. All we need to do is to define the getter
with the async keyword, as shown next.
The loadImage() method in this example always throws a noImage error to test
the code. The task checks for errors with a do catch statement and prints a
message on the console to report the result. Notice that when an
asynchronous function can throw errors, we must declare the throws
keyword after async.
Concurrency

Asynchronous tasks are useful when we want to free resources for the
system to perform other tasks, like updating the interface, but when we
want to run two tasks simultaneously, we need concurrency. For this
purpose, the Swift Standard Library defines the async let statement. To turn
an asynchronous task into multiple concurrent tasks, all we need to do is to
declare the processes with the async let statement, as shown next.
Task(priority: .background) {
async let imageName1 = loadImage(name: "image1")
async let imageName2 = loadImage(name: "image2")
async let imageName3 = loadImage(name: "image3")
When working with concurrent tasks, we could run into a problem called
Data Race. A data race happens when two or more tasks running in parallel
try to access the same data. For instance, they try to modify the value of a
property at the same time. This could lead to errors or serious bugs. To
solve this issue, the Swift Standard Library includes Actors.
Actors are data types that isolate parallel tasks from one another, so when
a task is modifying the values of an actor, other tasks are forced to wait.
Actors are reference types and are defined like classes, but instead of the
class keyword, they are declared with the actor keyword. Another important
difference with classes is that the properties and methods must be
accessed asynchronously (we must wait with the await keyword). This
ensures that the code can wait until the actor is free to respond (no other
task is accessing the actor).
The following example illustrates how actors work. The code declares an
actor with a property and a method, creates an instance, and then calls the
method from multiple tasks.
actor ItemData {
var counter: Int = 0
The interface includes a button to start two timers that repeat indefinitely,
one every 0.1 seconds and another every 0.2 seconds. The timers perform
a task with a concurrent operation that calls the incrementCount() method in
the Actor. This means that different tasks in different threads will be calling
the method, and eventually they will do it at the same time, creating a data
race. If we declare ItemData as a class, we will have errors, unexpected
behavior, or even crashes, but because we declared this data type as an
actor, the code works correctly. Every time a task calls the incrementCount()
method, the actor takes control and makes sure that the tasks have access
to the method one at a time.
As we mentioned, the actor isolates the properties and methods from the
rest of the code and other threads. This means that we can only access an
actor asynchronously (we must wait for the actor to allow access), but in
some circumstances, this isolation is not required. In these cases, we can
revert the condition of isolation with the following keyword.
actor ItemData {
var counter: Int = 0
let maximum: Int = 50
func incrementCount() -> String {
counter += 1
return "Value: \(counter)"
}
nonisolated func maximumValue() -> String {
return "Maximum Value: \(maximum)"
}
}
struct ContentView: View {
var item: ItemData = ItemData()
In these examples, we have worked with values defined by the actor, but
usually values are also sent to the actor for processing. Sending values to a
method in the actor is dangerous. Because an actor's job is to make sure
that two or more asynchronous tasks don't modify values simultaneously,
not every values is safe. Value types, including custom structures and
primitive data types like Int and String, are thread safe because they are
copied. When we call a method in the actor with one of these values, the
system creates a copy and sends that copy to the method, so the original
value is not modified. But objects are reference types and therefore only a
reference to the object is sent to the actor, which means that the object
may be modified from elsewhere in the code, potentially creating a data
race. To ensure that the values we want to sent to a method are safe, the
Swift Standard Library defines the following protocol and attribute.
The Sendable protocol doesn't do anything other than telling the compiler
that the data type is thread safe. When a data type conforms to this
protocol, the compiler shows errors if it includes unsafe values. For
instance, although structures are safe, we can make them conform to this
protocol to make sure we don't add any unsafe properties later. Classes are
also safe if they only include immutable values, but subclasses may not be
safe, so we have to mark the class with the final keyword to make sure that
nobody can create a subclass from it, as shown next.
init(name: String) {
self.name = name
}
}
actor ItemData {
var stock: Int = 100
This example defines a class called Product that is final (no subclasses can be
created from it) and includes an immutable property (let). Also, the
property is of type String, a sendable data type by default. This means that
objects created from this class are thread safe and can be sent to an actor.
If we really need to include unsafe values and we know for sure that they
are not going to be modified from different threads, we can tell the
compiler not to check for errors with the following attribute.
This is useful when we need to work with unsafe data types, or to send to
an actor a value produced by an old framework. For instance, in the
following example we turn the Product class into a structure and use the
name property to store an NSString value. The NSString data type is not
sendable and therefore the Product structure does not conform to the
requirements of the Sendable protocol, but because we know that the value
is not going to be modified anywhere else, we ask the compiler not to
worry about it with the @unchecked attribute, and Xcode doesn't show any
warnings or errors.
Listing 9-12: Asking the compiler not to check conformity to the Sendable
protocol

This code creates an asynchronous task as before, but now the method is
marked with @MainActor, so the code is executed in the main thread and we
can safely update the myText property and the interface.
Most of the time, only part of our code deals with the interface, but the
rest can be executed in the current thread. For cases like this, we can
implement the run() method. This is a type method defined by the MainActor
structure (the structure used to create the Main Actor). The method takes
a closure with the statements we need to execute in the main thread.
The loadImage() method now includes a statement at the end to print the
string on the console, but only the statement that assigns the new value to
the myText property needs to run in the main thread, so we execute it
within the run() method. Notice that this method is marked with await. The
await keyword is necessary because the method may have to wait for the
main thread to be free to execute the statements.
The run() method can also return a value. This is useful when we need to
report the result of a complex operation. All we need to remember is to
declare the type of value returned by the closure, as shown next.
On the other hand, the AsyncIteratorProtocol protocol only requires the data
type to implement the following method.
A task group is a container for dynamically generated tasks. Once the group
is created, we can add and manage tasks from code as required by the
application. The Swift Standard Library defines the following global
methods to create a group.
In this example, we create a task group that doesn't throw errors. The
group doesn't return a value either, but the tasks return a string, so we
declare the of argument of the withTaskGroup() method with a reference to
the String data type (String.self). The tasks are added to the group one after
another. Each task performs the same process as before. They call the
loadImage() method asynchronously and get a string in return.
Because a task group is an asynchronous sequence of tasks, we can iterate
though the values with a for in loop, as we did for the asynchronous
sequence created in the previous section of this chapter. Every time a task
is completed, the group returns the value produced by the task until no
tasks remain, in which case the value nil is returned to finish the loop.
There are two types of URL: secure and non-secure. Non-secure URLs are
identified with the http protocol (Hypertext Transfer Protocol) and secure
URLs are identified with the https protocol (Hypertext Transfer Protocol
Secure). Secure URLs are allowed by default, but if we need to open non-
secure URLs we must configure our app to circumvent a security system
called ATS (App Transport Security) implemented by Apple devices.
The option to configure the App Transport Security system is called "App
Transport Security Settings", and it is added to the app's configuration from
the Info panel. We have introduced this panel before (see Figure 5-13) and
use it to add custom fonts (see Figure 5-34). As explained before, new
options are added from the + button on the right side of the items.

After we click on the + button (circled in Figure 9-2), a new empty text field
is added below the option. If we start typing, a drop down menu shows the
options available and we can select it from the list.
The Allow Arbitrary Loads key takes a Boolean value specified with the
strings YES and NO (or 1 and 0, respectively). Setting this key to YES (1)
allows any URL to be opened. If what we want is to allow only specific
domains, we must use the Exception Domains key and add to the key
additional items with the domains we want to include. These items in turn
require at least three more items with the keys NSIncludesSubdomains
(Boolean), NSTemporaryExceptionAllowsInsecureHTTPLoads (Boolean), and
NSTemporaryExceptionMinimumTLSVersion (String). For example, the following
configuration allows documents from the formasterminds.com domain to
be opened.
Figure 9-5: App Transport Security configured to allow URLs from
formasterminds.com
All the AsyncImage view needs to download and display an image is a URL. In
this example, we store a URL in a constant and then implement the view to
load the image. Although the image is effectively loaded and displayed, the
AsyncImage view doesn't allow any configuration, so the image is shown in
its original size.
Figure 9-6: Image loaded asynchronously
When we provide the content argument, the AsyncImage view relegates the
job of displaying the image to the Image view received by the closure, so we
can configure this view as before. In this example, we resize the image with
the resizable() modifier and scale it to fit within the view with the scaledToFit()
modifier. Notice that we have also defined the placeholder argument to
show a temporary image while the final image is downloading.
The User Defaults system can store any amount of data we want, but it is
recommended to use it to store short strings and small values. Its main
purpose is to store the app’s settings. For instance, we can use it to allow
the user to store a limit on the number of items managed by the
application, and then set that limit back every time the app is executed.
To create this application, all we need is to define a property with the
@AppStorage property wrapper and then use it as we do with a state
property.
This view defines an @AppStorage property with the "counter" key and the
name mycounter, and includes a Stepper and a Text view to let the user select a
value and show the current value on the screen.
An @AppStorage property is used as any other state property before, but
now the value is stored in the User Defaults system. In consequence, when
the app is closed and opened again, the mycounter property gets the value
from User Defaults, and the latest value stored by the user is shown on the
screen.
This view creates a property of type TimeInterval called interval with the
number of seconds from a reference date (January 1st, 2001). When the
view appears, the onAppear() modifier turns this value into a Date structure,
extracts the components of the date, creates a string with these values,
and assigns it to a state property to show it on the screen. At the end, we
update the interval property with the current interval to be able to calculate
the time again when the app is relaunched.
The previous examples were designed for didactic purposes. User Defaults
is frequently used to store the app's settings, and users are provided a
separate view where they can change these values and configure the app
to their liking. In the following example, we are going to follow this
approach to allow the user to configure the rows of a List view that
presents a list of books. By modifying the values from the Settings view, the
user can define the corners of the book's cover and hide the cover and the
year.

The model for this project needs the usual Book and BookViewModel
structures implemented in Chapter 7, Listing 7-3, and the observable
object must include the @Published property to store the books, and also the
@AppStorage properties to store the app's settings.
init() {
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
year: 2011, selected: false)),
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover:
"book2", year: 2017, selected: false)),
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)),
BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W.
Kernighan", cover: "book4", year: 1988, selected: false)),
BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover:
"book5", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)),
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover:
"book7", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer",
cover: "book8", year: 1982, selected: false)),
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover:
"book9", year: 1993, selected: false)),
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
1983, selected: false)),
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
selected: false)),
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
"book12", year: 2007, selected: false))
]
}
}

In addition to our usual userData property, we have defined three
@AppStorage properties. The cornerSize property stores a Double value that
determines the corner radius of the book's cover, and the showYear and
showCover properties store Boolean values that indicate whether the year
and the cover should be shown or hidden.
Now, we can use these properties to configure the list of books. The
following view includes a List view to display the books embedded in a
NavigationStack view.
Each row on the list is configured according to the values in User Defaults.
The Image view is only displayed when the value of the showCover property is
true, the corner radius of the book's cover is determined by the value
stored in the cornerSize property, and the year is shown depending on the
value of the showYear property.
The navigation bar of the ContentView view includes a button on the right to
open a view called SettingsView. This is the view where we allow the user to
change the configuration.
This view includes a Slider view to assign a value between 0 and 30 to the
cornerSize property, and two Toggle views to toggle the values of the showCover
and showYear properties. When the user modifies any of these values, the
values in User Defaults are updated, and the views are redrawn, as shown
in Figure 10-3, above.
Do It Yourself: Download the book covers from our website and add
them to the Asset Catalog. Create a Swift file called
ApplicationData.swift for the model in Chapter 7, Listing 7-3. Update
the ApplicationData class with the code in Listing 10-3 and the
ContentView view with the code in Listing 10-4. Create a SwiftUI View
file called SettingsView.swift for the view in Listing 10-5. Remember
to inject the ApplicationData object into the environment for the app
and the previews (Chapter 7, Listing 7-4). Run the application on the
iPhone simulator. You should see the interface in Figure 10-3, left.
Press the Settings button. In the Settings view, change the corner
radius and turn the switches off (Figure 10-3, center). Press the Back
button to see the changes on the interface (Figure 10-3, right).
10.2 Files

The User Defaults system is meant to be used to store single values for
configuration. For large amounts of data, we must create our own files. To
manage files and directories, the Foundation framework defines a class
called FileManager. One object of this class is assigned to the app and from
that instance we can create, delete, copy, and move files and directories in
the storage space reserved for our application. The class offers the
following type property to get a reference to this object.
Apple's operating systems allow users to create and access any files and
directories they want, but applications are restricted to their own storage
space to ensure that they do not interfere with each other. This means that
we can only access the files and directories that belong to our application.
When the application is installed on the device, the system creates a group
of standard directories that we can use to store our files. The most useful
are the Documents directory, where we can store the user’s files, and the
Application Support directory, for files our app needs to create during run
time that are not directly generated by the user. The location of these
directories is not guaranteed, so we always must ask the system for the
current URL that points to the directory or file we want to access. To get
the location of common directories like Documents, the FileManager class
includes the urls() method. This method requires two arguments. The first
argument is an enumeration with values that represent different
directories. There are several values available, including documentDirectory to
reference the Documents directory and applicationSupportDirectory to
reference the Application Support directory. The second argument is
another enumeration with values that indicate the domain where the
directory is located. The system organizes directories and files in separate
domains depending on their intended usage. The domain where our app’s
files are stored is the User Domain, identified with the constant
userDomainMask. In consequence, to get the URL of any of the directories
generated for our app and create files to store the user’s data, we must get
a reference to the FileManager object and then call the urls() method with the
values that represent the location we want to use.
Although we can read and create files from anywhere in the code, it is
recommended to manage all our files from the model. The following is a
simple model with an observable object to illustrate how to access the
directories available for the application and how to create a file.
Listing 10-6: Accessing directories and creating files

import SwiftUI
This model defines a method called saveFile() to create a file with the name
specified by the argument. The method gets a reference to the FileManager
object assigned to the application and then calls the urls() method on the
manager to get the URL of the Documents directory. The urls() method
returns an array of optional URL structures with all the possible locations of
the directory. In the case of the Documents directory, the right value is
always the first item in the array. Once we have this URL, we add the name
of our file to it with the appendingPathComponent() method. This method adds
a string to the end of the URL and takes care of including the necessary
forward slashes to ensure that it is valid. Because we are working with
paths, we get the URL's path from the path property and finally call the
createFile() method to create the file. (If the file already exists, the method
updates its content.)
The model is ready, now we must create the view. The idea is to create a
simple project that later we can expand to add more functionality.
Therefore, we are going to need a simple view to present the files and
open a sheet to let the user add a new one, as shown below.
Figure 10-4: Interface to create files
For the moment, the view only displays a message on the screen, but it
provides a button in the navigation bar to open a sheet with a view called
AddFileView that allows the user to create new files.
Listing 10-8: Creating new files

struct AddFileView: View {
@EnvironmentObject var appData: ApplicationData
@Environment(\.dismiss) var dismiss
@State private var nameInput: String = ""
This view includes a TextField view for the user to insert the name of the file
and a Button view to create it. When the button is pressed, we trim the
string to remove white spaces at the beginning and the end, add the ".txt"
string to the name (adding an extension is not necessary), and then call the
saveFile() method in the model to create the file. After this, the Documents
directory designated to our app will contain an empty file with the name
inserted by the user and the txt extension.
Do It Yourself: Create a Multiplatform project. Create a Swift file
called ApplicationData.swift for the model in Listing 10-6. Update the
ContentView view with the code in Listing 10-7. Create a SwiftUI View
file called AddFileView.swift for the view in Listing 10-8. Remember
to inject the ApplicationData object into the environment for the app
and the previews (Chapter 7, Listing 7-4). Run the application on the
iPhone simulator and press the Add File button. Insert the name of
the file and press the Create button. The file is created, and the
sheet is closed.
At the moment, the initial view only shows the message "No Files", but the
purpose of this application is to allow the user to create multiple files and
show them on the screen. For this purpose, the FileManager class includes
methods to list the content of a directory. For instance, we can use the
contentsOfDirectory() method to get an array of strings with the names of the
files and directories in a specific path, store them in an array, and then
present them on the screen with a List view. For this purpose, we need a
model to store the names of the files along with an id, as we did for books
in previous models, and a @Published property to provide the list to the
views. The following is the new model for this project.
The first structure, called File, provides the model. It conforms to the
Identifiable protocol and includes the corresponding id property to identify
each value, and a property called name to store the file's name. Next, we
define a property to store a reference to the FileManager object assigned to
the application and another to store the URL of the Documents directory.
In the initializer, we get a reference to the FileManager object with the default
property and assign it to the manager property. Then, we get the URL of the
Documents directory with the urls() method and assigns it to the docURL
property. After the properties are initialized, we get the list of files in the
directory with the contentsOfDirectory() method. This method returns an array
of strings with the name of the files, so we create a loop, initialize an
instance of the File structure with each name, and add them to the listOfFiles
array. Notice that the method throws errors, so we use the try? keyword to
handle them and only store the name of the files if the value returned is
not nil.
The saveFile() method is the same as before, but after creating a new file we
now check if the name of the file already exists in the listOfFiles property
and only add an instance of the File structure to the array if there isn't
already a file with that name.
After the ApplicationData structure is initialized, the listOfFiles property
contains the names of all the files in the directory, so we can show them to
the user.
The List view includes a ForEach view that lists the values of the listOfFiles
property. Each row includes the name of a file, as shown below.
Figure 10-5: List of files
The same way we create a file, we can create a directory. The method to
create a directory is called createDirectory(). The method takes three values:
the path of the new directory (including its name), a Boolean value that
indicates if we want to create all the directories included in the path that
do not exist (in case the path includes directories we have not created yet),
and the directory’s arguments. This method throws an error if it cannot
complete the task, so we must handle the error with the try keyword. Using
this method, we can expand our project to store files in two directories
called original and archived. The original folder will contain the files
created by the user, and the archived folder will contain the files the user
decides to archive. The following is the model we need for this application.
init() {
listOfFiles = [0: [], 1: []]
currentDirectory = 0
manager = FileManager.default
docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!
The ContentView view in Listing 10-12 includes a Picker view with the values in
the directories property ("original" and "archived"). When the user selects
one or the other, the value of the currentDirectory changes, and this tells our
model which directory the user is currently working on. To create the list of
files, we read again the value of this property. If the value is 0, we list the
files of the original directory, but if the value is 1, we list the files of the
archived directory.
One thing we must contemplate when working with dictionaries, is that
they return an optional. Therefore, the ForEach view won't work unless we
provide an alternative value. In this case, we use the nil-coalescing
operator (??) to create the list with an empty array if the value of the
currentDirectory property does not exist in the dictionary (it is different than 0
or 1).
Because we want the user to be able to add new files only to the original
directory, we applied the disabled() modifier to the Add File button in the
navigation bar. If the user is working on the archived directory, the button
is disabled, as shown below.
Now, we can allow the user to archive files and delete them. For this, we
need to move files from the original directory to the archived directory and
remove them from storage. The FileManager class includes the moveItem() and
removeItem() methods for this purpose. The following are the methods we
need to add to our model to be able to move and remove files from the
view.
The first method is called deleteFile() and it receives the name of the file to
remove. Because the user can only delete files from the directory in which
is currently working, we use the value of the currentDirectory property to
define the file's URL. With this URL, we call the removeItem() method on the
FileManager object to delete the file. If the operation is successful, we also
remove the name of the file from the array corresponding to the current
directory with the removeAll(where:) method, so the file is removed from the
array and the screen.
The next method is called moveToArchived(). The purpose of this method is to
move the file selected by the user from the original directory to the
archived directory. For this purpose, we define two URLs, one to indicate
the current location of the file (\(directories[0])/\(name)) and another to indicate
where the file is going to be created and with what name (\(directories[1])/\
(name)). Before doing anything, we check if the file already exists at the
destination with the fileExists() method. If the file doesn't exist, we use those
two URLs to call the moveItem() method on the FileManager object and move
it. If the operation is successful, we still must update the information in the
model. First, we remove the file from the array of the original directory (0)
with the removeAll(where:) method. This removes all the strings in the array
that are equal to the name of the file we are moving. And second, we add
the file to the array corresponding to the archived directory, so the name
appears on the right folder.
The view now needs to provide the tools for the user to move and remove
each file. For this example, we have decided to generate the rows with a
custom view and include two buttons per row, one to move the file to the
archived folder and another to delete it.
Nothing changes in the view, except that instead of showing the name of
the file with a Text view, we now create a RowFile view to include the
buttons. The buttons are created with SF Symbols, one that represents a
folder and another that represents a trash can. The button with the image
of a folder calls the moveToArchived() method in the model to move the file to
the archived directory, but it is only displayed when the current directory is
original (currentDirectory == 0). On the other hand, the button with the image
of a trash can is displayed in both directories and calls the deleteFile()
method in the model to delete the file. Notice that we declare the styles
for the buttons as plain. This makes the buttons responsive when they are
inserted in a row (the buttons have precedence over the selection of the
row).
Figure 10-7: Buttons to move and delete a file
Some applications need to know more than the name of the file. The
FileManager class offers the attributesOfItem() method to get the file's
attributes, such as the date the file was created or its size. The method
returns a dictionary with predefined keys to identify each value. There are
several constants available we can use as keys. The most frequently used
are creationDate (the date the file was created), modificationDate (last time it
was modified), size, and type. The following method shows how to read
these keys to get the attributes of the file selected by the user.
if manager.fileExists(atPath: filePath) {
if let attributes = try? manager.attributesOfItem(atPath: filePath) {
let type = attributes[.type] as! FileAttributeType
let size = attributes[.size] as! Int
let date = attributes[.creationDate] as! Date
if type != FileAttributeType.typeDirectory {
values.0 = file.name
values.1 = fileURL.pathExtension
values.2 = String(size)
values.3 = date.formatted(date: .abbreviated, time: .omitted)
}
}
}
}
return values
}

The getDetails() method receives a UUID value to identify the file the user
wants to read. From this value, the method gets the name of the file and
builds the path. Once we get the path to the file, we are able to call the
attributesOfItem() method to read its attributes. The method returns the
values in a dictionary, but they are of type Any, so we must cast them to the
right types. For instance, the type value must be converted into a
FileAttributeType structure, the size into an Int, and the creationDate into a Date
structure. The FileAttributeType structure determines the resource's type. The
structure includes properties to represent different types of resources. The
most frequently used are typeRegular to represent files and typeDirectory to
represent directories. By reading these properties, we can determine if the
item is a file or a directory. This is useful when in addition to files our
application allows the user to create folders. If the item is a file, we assign
the attributes to a tuple and return it.
To show the file's attributes to the user, we have decided to create an
additional view that opens when the user taps on a file on the list. We call
it FileDetailsView.
This view receives the file's id from the list and then calls the getDetails()
method in the model with that value. The tuple returned is assigned to the
values constant, and finally the constant is used to show the attributes to
the user (values.0 is the name, values.1 is the extension, values.2 is the size, and
values.3 is the date).
Of course, to open this view we must embed each row in a NavigationLink
view that creates an instance of the FilesDetailsView structure with the file's
id. The following are the modifications we must introduce to the List view
in the ContentView view for this purpose.
Notice that we send the value of the id property to the FileDetailsView view to
work with the file's id, not its name. The result is shown below.
Storage systems, like hard drives and solid-state drives, store information
the only way a computer knows, as a series of ones and zeros. Therefore,
the information we want to store in files has to be converted into a stream
of Bytes that can be later turned back into the original values. For this
purpose, the Foundation framework includes the Data structure.
Although we can work directly with a Data structure, most frameworks
provided by Apple include tools to convert our data into Data structures.
For instance, to work with images, the UIKit framework includes the
UIImage class. This class can convert an image into data, and create an
image from data, strings, or other images. Once we have this value, we can
turn it into an Image view with the Image(uiImage: UIImage) initializer
introduced before. The following are some of the initializers provided by
the class.
The UIImage class includes properties and methods to get information about
the image and process it. The following are the most frequently used.
size—This property returns a CGSize value with the size of the image.
scale—This property returns a CGFloat value with the scale of the
image.
imageOrientation—This property returns a value that identifies the
image's orientation. It is an enumeration called Orientation. The values
available are up, down, left, right, upMirrored, downMirrored, leftMirrored, and
rightMirrored.
To convert an image into data, the UIImage class includes the following
methods.
The process to load an image from file is simple. Once we get the data with
the contents() method provided by the FileManager class, we convert it into an
image with the UIImage(data:) initializer. On the contrary, storing an image in
a file requires a few more steps. The image must be loaded with a UIImage
object, then the object must be converted into a Data structure with the
pngData() or jpegData() methods, and finally stored in a file with the createFile()
method of the FileManager object implemented before. This method creates
a file if it doesn't exist or updates its content otherwise, so we can use it to
store different images. In the following example, we implement it to store
the image selected by the user. If the user selects another image later, we
just replace the old image in the file with the new one. As always, most of
the process is performed by the model, as shown next.
init() {
manager = FileManager.default
docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!
The Image view in Listing 10-19 is created from the UIImage value stored in
the imageInFile property (the image stored in the file). Notice that the
UIImage initializer always returns an optional, so we must provide an
alternative image in case the file can't be loaded, or it doesn't exist. For
this purpose, we use the nil-coalescing operator (??) to load the image
nopicture from the Asset Catalog.
The Image view includes the onTapGesture() modifier to open a sheet when
the image is tapped by the user. In the view opened by the sheet, we are
going to show to the user three images to select from. We call this view
SelectPictureView.
The images are inside a horizontal ScrollView view and are embedded in a
ZStack to be able to display a button on top that the user can tap to select
the image. When the button is pressed, we call the saveFile() method in the
model with the name of the selected image and that image is stored in the
file. The result is shown below.
The size of photographs taken by the camera or images loaded from the
Photo Library are often too large for processing and storage. Storing these
images in files and databases can consume too much storage space and
memory. The UIImage class includes the following methods to optimize an
image and reduce its size.
The process performed by the saveFile() method is the same, but before we
convert the image to data and store it in the file, we reduce its size to 100
points, minimizing the use of memory and storage space.
Figure 10-10: Image reduced
Besides the UIImage class, there are other classes and structures available in
Apple's frameworks that can turn values into a Data structure for storage.
For instance, the String structure includes a method that turns a string into
data an also an initializer that can get back the string from a Data structure.
The String structure also includes a convenient method to turn a string into
data and store it in a file, all at once.
To store text in a file and read it back, we must follow the same procedure
used for images. We must convert the string to data and then the data
back to a string. The following example shows how to do this with the text
inserted by the user in a TextEditor view. The difference from previous
examples is that these types of applications usually store the text as the
user types or deletes a character, which means that we must save the file
each time a new value is assigned to the @Published property. For this
purpose, we can use property observers, as we do in the following model.
init() {
manager = FileManager.default
docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!

Do It Yourself: Create a Multiplatform project. Create a Swift file
called ApplicationData.swift for the model in Listing 10-22. Update
the ContentView view with the code in Listing 10-23. Remember to
inject the ApplicationData object into the environment for the app and
the previews (Chapter 7, Listing 7-4). Run the application on the
iPhone simulator. Type a text. Stop the application from Xcode and
run it again. You should see the same text on the screen.
Bundle

In previous examples, we have worked with files created by the user, but
sometimes we need to access files added to our project during
development. The problem is the app’s files, code, and resources are not
stored in a single directory; they are encapsulated in a bundle. Bundles are
directories assigned to each application by the system. They create a
hierarchical structure to organize all the app’s files and resources.
To create and manage bundles, Foundation includes the Bundle class. The
class offers properties and methods to work with bundles and get their
location, including a type property that returns a reference to the bundle
created by default for our application.
Because we are not able to determine the location of our app’s files and
resources during development, every time we want to access these files
from code, we must get their URLs from the Bundle object. The class
provides the following methods for this purpose.
init() {
let manager = FileManager.default
let bundle = Bundle.main
if let path = bundle.path(forResource: "quote", ofType: "txt") {
if let data = manager.contents(atPath: path) {
if let message = String(data: data, encoding: .utf8) {
textInFile = message
}
}
} else {
textInFile = "File Not Found"
}
}
}

The model's initializer in Listing 10-24 gets a reference to the app’s bundle
and then finds the path for the quote.txt file with the path() method. After
we get the path, we can read the file as we did before. As always, the value
is stored in the @Published property to update the views.
The FileManager class provides the tools we need to create and store files in
the storage space designated by the system for our application, but
sometimes users need to share that information with other applications.
For this purpose, Apple systems allow users to create and share
documents. Documents are containers that can process and store a specific
type of information in a file. They can be stored on the device and shared
with other apps, sent to servers, or stored in iCloud to make them available
to other devices.
Enabling apps to create, edit, and share documents is so important these
days that SwiftUI includes tools to create a Document app (an application
with the sole purpose of managing documents). The system includes a
predefined interface where users can manage all the documents available
in their accounts and create new ones.
One of the things defined by the document model is the type of data the
document can handle. This is determined by the UTType structure. This
structure provides a universal identifier that every application can
recognize. Although we can define our own, as we will see in further
chapters, the structure provides type properties to represent the most
common types, including png, gif, jpeg, pdf, mp3, avi, json, image, text, and
plainText. In this example, we assign the plainText type to the
readableContentTypes property to configure the document to store plain text.
(Notice that to use the UTType data type, we had to import the
UniformTypeIdentifiers framework where the structure is defined.)
To store the document's content, we define a String property called
documentText and use the structure's initializer to initialize it with an empty
string. Every document created from this model, will be empty by default.
The document model requires an initializer to read the document's
content, and a method to write it. The initializer is used to recreate the
model when the user selects the document. The system reads the
document's file and provides the data with a FileWrapper object that we can
read from the regularFileContents property. So we read this property, turn the
data into a string with the String initializer, and assign it to the documentText
property. To write the content of this property back to the document, we
implement the fileWrapper() method. In this case, all we need to do is to turn
the value of the documentText property into a Data structure, create a
FileWrapper object with it, and return it.
IMPORTANT: Notice that if the process of reading the document
fails, we must return an error. The Foundation framework defines a
class called CocoaError to return common error codes. The class
includes an initializer to create an object from a Code structure, and
several type properties to return Code values. The most frequently
used for reading files are fileReadCorruptFile, fileReadNoSuchFile, and
fileReadUnknown.
Now that the document model is ready, we can create the Scene with the
DocumentGroup structure. The DocumentGroup structure's initializer requires an
instance of our document model and a closure with the view we want to
use to edit the documents. This closure receives a FileDocumentConfiguration
structure with a reference to the document and its properties. The
structure includes the following properties to return these values.
@main
struct TestApp: App {
var body: some Scene {
DocumentGroup(newDocument: TextDocument(), editor: { config in
ContentView(document: config.$document)
})
}
}

This application is similar to the previous one (see Listing 10-23), but
instead of storing the text in a file, we store it in a document. A Document
app allows the user to store and share documents on the device, iCloud, or
a server. The provided user interface includes two tabs, one with the
recent files, and another where we can select the location where we want
to store the document and an option to create a new one, as shown below.
When we press the + button, the app creates a new document and opens
the ContentView view to allow the user to edit its content.
The DocumentGroup structure provides an interface with all the tools the user
needs to create, modify, and remove documents, but sometimes all we
need is to allow the user to export or import documents from our own
custom interface. For this purpose, SwiftUI includes the following
modifiers.
The same way we can create a document with the text inserted by the user
and export it, we can import a document, read it, and show the text to the
user by implementing the fileImporter() modifier. The only difference is that
instead of working with a document, we must work directly with the file
associated to the document.
The fileImporter() modifier returns a URL structure with the location of the
file. To read the file at this URL and extract the data, the Data structure
includes the following initializer.
Because the files we access with this modifier are not stored in our app's
storage space but in the file system, they are protected and inaccessible by
default. To grant access to these files for our application, the URL structure
includes the following methods.
The fileImporter() modifier imports the document and calls the closure
assigned to the onCompletion argument to report the result. This closure
receives a Result value that we can read to get the file's URL (see Chapter 3,
Listing 3-195). In this example, we use the get() method to get the URL and
then read the file with the Data(contentsOf:) initializer. The data is converted
into a string and assigned to the document, so the text is shown on the
screen.
Because we are going to manage the document from the data model, we
can apply the fileExporter() modifier to the ContentView in the App structure.
@main
struct TestApp: App {
@StateObject var appData = ApplicationData()
This modifier works like before, but now the property that determines
when to open the interface is a @Published property in the model. The
application is going to allow the user to create files, edit their content, and
export them, so the model needs a few more properties and methods, as
shown next.
Listing 10-32: Managing files and documents from the model

import SwiftUI
init() {
manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
docURL = documents.first!
document = TextDocument()
selectedFile = FileContent(name: "", content: "")
We have defined two structures to store the data: one called File to store
the files and another called FileContent to manage the content of the
selected file. To know which file was selected by the user, we define
another @Published property called selectedFile. The document is stored in a
normal property called document. When the model is initialized, we defined
all the values as before, assign an instance of the TextDocument structure to
this property, and initialize the selectedFile property with an empty FileContent
structure (no name and no content).
The views are going to offer buttons to add new files to the list, to save the
text, and one on each row to export the file as a document, but all the
work is managed by the methods in the model. We have the saveFile()
method to add a new file to the list, the saveContent() method to save the
text inserted by the user, the exportDocument() method to export a file as a
document, and the getDocumentContent() method to read the content of the
selected file when the user decides to open or export it.
The following is the main view. In this view, we show the list of files created
by the user and provide a button to add more.
This view includes a button in the navigation bar to open a sheet with the
AddFileView view used in previous examples to create new files (see Listing
10-8).
The list of files is built with a NavigationLink view that includes the file's
name and a button to call the exportDocument() method in the model to
export the file as a document. When a row is selected, the NavigationLink
view opens a new view called EditFileView() to allow the user to edit the file's
content.
This view receives a copy of the File structure representing the selected file
and includes a TextEditor view to allow the user to edit the file's content. But
this is actually managed by an instance of the FileContent structure stored in
the selectedFile property, so we implement the onAppear() method to get the
content of the file and assign the current text to the structure's content
property. Now the TextEditor view displays the content of the file and allows
the user to edit it. To save the changes, the navigation bar includes a Save
button that calls the saveContent() method in the model. This method
converts the text into data and stores it in the file.
The application is finally ready. The user can create new files, edit their
content, and export them, as shown in Figure 10-17.
Do It Yourself: Update the App structure with the code in Listing 10-
31. Create a Swift file called ApplicationData.swift for the model in
Listing 10-32. Update the ContentView view with the code in Listing 10-
33. Create a SwiftUI View file called EditFileView.swift file for the
view in Listing 10-34. You will also need a SwiftUI file called
AddFileView.swift for the view in Listing 10-8. Run the application on
the iPhone simulator. Press the Add File button to add a new file.
Select the file and write some text. Press the Save button to save it.
Press the Export button on the file's row. You should be able to
create a document with the file's content.
10.3 Archiving

The methods we have just implemented to store data in files are enough
for simple models but present some limitations. We can only work with
single values and with classes that already provide a way to turn their
content into data. Professional applications rely on more elaborated
models that include collection of values and custom data types. To give us
more flexibility, Foundation offers the NSCoder class. This class can encode
and decode values to Data structures for storage purposes in a process
called Archiving.
An NSCoder object not only encodes an object but also the objects it is
connected to, preserving the connections and the hierarchy. For example,
we may have two objects with properties that reference the other object.
Object 1 references Object 2 and Object 2 references Object 1. With
archiving, both objects are encoded, stored, and then decoded and
connected again when we need them, conforming a structure called Object
Graph.
Encoding and Decoding

The NSCoder class provides all the methods necessary to encode and
decode the values of an object, but all the work is done by instances of two
NSCoder subclasses called NSKeyedArchiver and NSKeyedUnarchiver. The
NSKeyedArchiver class calls the encode() method on the objects to encode their
values and stores the data in a Data structure or a file. On the other hand,
the NSKeyedUnarchiver class initializes the objects with the protocol’s
initializer and returns the original values.
The NSKeyedArchiver class offers the following type method to encode an
Object Graph and store it in a Data structure.
And the NSKeyedUnarchiver class offers the following type method to decode
an Object Graph from a Data structure.
When the view defined in Listing 10-35 appears on the screen, the
onAppear() modifier encodes a single string and stores it in a file called
quotes.dat. As we did in previous examples, we first check if the file exists
with the fileExists() method and then proceed accordingly. If the file does
not exist, we convert the string to a Data value with the archivedData()
method and create a file with it, but if the file already exists, we read its
content with the contents() method and decode the data with the
unarchivedObject() method to get back the string.
The unarchivedObject() method can only work with data types that conform to
a protocol called NSSecureCoding. That is the reason why we had to specify
the NSString class as the value of the ofClass argument and convert it at the
end to a String structure with the as operator. (The NSString class conforms to
the NSSecureCoding protocol but the String structure does not.)
init() {
manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
docURL = documents.first!
bookInFile = BookViewModel(book: Book(title: "", author: "", year: 0, cover: nil))
As in previous examples, the Book structure is our data model, but now it
conforms to the Codable protocol, so we can encode it into a Data structure
and store it in a file. The structure includes properties to store the title,
author, cover, and year of publication.
The observable object includes a @Published property to store the book, an
initializer that reads the values from a file, a method to store new values,
and a method to store the book's cover. The initializer reads the file,
creates an instance of the PropertyListDecoder class, and calls the decode()
method on it to decode the data into a Book structure (Book.self). If
successful, the structure is assigned to the book property of the
BookViewModel structure stored in the bookInFile property to make the values
available for the view. The saveFiles() method performs the opposite process.
It creates an instance of the PropertyListEncoder class and calls the encode()
method on it with the instance of the Book structure created by the user to
convert the values into data and store it in the file.
When storing data in files or databases, it is recommended to store the
images in separate files. This is the job of the storeCover() method. This
method is called before the saveFile() method to store the book's cover in a
file and return the file's name to include it in the data model with the rest
of the values. In this example we always use the same image and the same
file for the book's cover, but in a real application the image is usually taken
from the device's Photo library or the camera, and each image is stored in
different files. (We will see how to name these files in the next project and
how to access the Photo library and the camera in Chapter 18.)
As always, all the work is done by the model. Therefore, all the view needs
to do is to read the values from the view model, show them on the screen,
and provide the user the possibility to modify them.
This view opens a sheet with an instance of the InsertBookView view when a
button in the navigation bar is pressed. The view allows the user to modify
the book's values.
The previous model stores the values of one book. If new values are
inserted, a new Book structure is instantiated with those values and stored
in the same file, replacing the previous one. This example illustrates how
custom data types, like the Book structure, are encoded and stored in files,
but most of the time we need to work with collections of values. For this
purpose, all we have to do is to encode an array of structures instead of a
single one. The following are the modifications required in our observable
object to store multiple books.
init() {
manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
docURL = documents.first!
Every time the values in the model are modified, either because a book is
added or removed, or the information of a book is updated, we must
update the data in the file. For this purpose, we define a method called
saveModel(). This method prepares the data and stores it in a file called
userdata.dat. In this example, we store in the file instances of the data
model (the Book structure), but the observable object stores an array of
BookViewModel structures. These are instances of the view model used to
prepare the values for the view. To convert this array into an array of Book
structures, the method extracts the values with the map() method. This
method processes the items in the array and returns the instance of the
Book structure inside each BookViewModel structure, creating the list of Book
structures we need to store in the file.
In this example, we only let the user add books. The books can't be erased
or modified. This is why there is only one method to edit the information
called addBook(). This method receives an instance of the Book structure with
the values inserted by the user, adds it to the array in the userData property,
and calls the saveModel() method to store all the values in the file again, so
the file always contains the latest information.
The initializer performs the opposite process of the saveModel() method. It
reads the content of the file, decodes the data with the decode() method
into an array of Book structures ([Book].self), and then converts the Book
structures into BookViewModel structures with the map() method before
assigning them to the userData property to make the values available for the
views.
Another change introduced in this observable object is the name assigned
to the files for the covers in the storeCover() method. We are still using the
same image for every book (bookcover), but because the user can now
store multiple books, we define the file's name with a UUID value to make
every name unique.
The view must create a list with all the books in the userData property and
provide a button for the user to insert new ones.
This view shows the list of the books stored in the file with a List view. The
navigation bar includes a button to open the InsertBookView view defined
before to let the user add new books. Therefore, the application works like
before, but now instead of one the user can insert and store multiple
books.

Do It Yourself: Update the observable object in your model (the
ApplicationData class) with the code in Listing 10-39 and the
ContentView.swift file with the code in Listing 10-40. Run the
application on the iPhone simulator. Press the + button to add a
book and press the Save button to save it. Stop and run the
application again. You should see all the books on the screen.
JSON

Another way to encode and decode custom data types is with JSON
(JavaScript Object Notation). This format was created to transmit
information on the web, but the fact that it is a very simple format to read
and process, made it suitable to store data for applications. Apple systems
adopted JSON long time ago to store and retrieve information. Foundation
includes the JSONDecoder class to decode JSON data into Swift structures
and the JSONEncoder class to encode Swift structures into JSON data. The
classes define their respective methods to decode and encode the values.
A JSON file is just a text file that stores the data in a specific syntax. The
syntax includes the values of an object or a structure as key/value pairs
separated by a colon, like dictionaries. The name of the property becomes
the key for the value, and each object or structure is enclosed in braces.
The following example shows how a JSON file looks like if we store one
instance of our Book structure.
init() {
manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
docURL = documents.first!
Of course, JSON can format nested structures and objects. For instance, we
may have a Book structure with a property that stores another structure
with information about the publisher.
Notice that the date in JSON doesn't look like a date. This is because Date
values contain the difference in seconds from a specific date in the past to
the date they represent. To convert these values into values of type Date,
we must declare what kind of format the decoder has to use. For this
purpose, the JSONDecoder class includes the following property.
Dates in JSON are shared in different formats, but when working with Date
values, we just have to declare the deferredToDate formatting and the
decoder will have enough information to decode the value back into a Date
structure.
The following is a simple model that illustrates how to read and process
these values. The observable object in this model loads a JSON file from
the bundle with the code introduced in Listing 10-44 and decodes it into
the structures introduced in Listing 10-43.
Besides the Publisher and Book structures introduced before, this model also
includes the BookViewModel structure to prepare the values for the view, and
the observable object to load the data. The initializer looks for a file called
template.json. The code assumes that the file has a structure like the
example in Listing 10-44. If found, the content of the file is loaded with the
contents() method of the FileManager object, decoded with a JSONDecoder
object, and the Book structure resulting from this process is assigned to the
bookInFile property to make it available for the view. The following is the
view we need to show these values on the screen.
Figure 10-20, below, shows what we see if the project includes a file called
template.json in the bundle with the JSON code of Listing 10-44.
The structure of the Core Data’s Object Graph is defined with a data model.
This has nothing to do with the data model of the MVC pattern
implemented in previous examples. A Core Data model is the definition of
the type of objects the graph is going to contain (called Entities) and their
connections (called Relationships).
A model can be created from code, but Xcode offers a practical editor to
define the structure of the graph. The model is stored in a file and then the
file is compiled and included in the Core Data system created for our
application. Xcode offers a template to create this file.
Figure 10-21: Option to create a Core Data model in the iOS panel
The file may be created with any name we want but it must have the
extension xcdatamodel. Once created, it is included in our project along
with the rest of the files. Clicking on it reveals the Xcode editor in the
Editor Area.
Every attribute must be associated with a data type for the objects to know
what kind of values they can manage (Figure 10-24, number 2). Clicking on
the attribute’s type, we can open a menu to select the right data type. The
most frequently used are Integer 16, Integer 32, Integer 64, Double, Float,
String, Boolean, Date, and Binary Data. The Integer 16, 32, or 64 options
are for Int16, Int32, and Int64 values, Double and Float are for Double and Float
values, String is for String values, Boolean is for Bool values, Date is for Date
values, and Binary Data is for Data values.
An entity may contain as many attributes as our objects need. For example,
we may add a few more attributes to complement the information
required for books.
In this example, we have added an attribute called year to store the year in
which the book was published, and two attributes of type Binary Data to
store images (the book's cover and thumbnail). The data types used by
these attributes are analog to Swift data types. The title attribute takes a
String value, the year attribute stores a value of type Int32, and the images
are stored as Data structures.
Most values don't require much consideration, but images are made of big
chunks of data. Storing large amounts of data in a Persistent Store can
affect the system's performance and slow down essential processes like
searching for values or migrating the model. One alternative is to store the
images in separate files, but as we have seen in previous examples, this can
get cumbersome. Fortunately, Core Data can perform the process for us.
All we need to do is to store the image as Binary Data and select the option
Allows External Storage, available in the Data Model inspector panel inside
the Utilities Area, as shown below. After the option is selected, the images
assigned to that attribute are stored in separate files managed by the
system.

We could have also included another attribute for the author’s name, but
here is when we need to think about the structure of the Object Graph and
how the information will be stored. If we include a String type attribute for
the author's name inside the Books entity, every time the user inserts a
new book it will have to type the name of the author. This is error prone,
time consuming, and when several books of the same author are available,
it is impossible to make sure that all share the same exact name (for
example, one book could have the author’s middle name and others just
the first one). Without the certainty of having the exact same name, we
can never incorporate features in our app such as ordering the books by
author or getting the list of books written by a particular author. Things get
worse when, along with the name, we also decide to store other
information about the author, like his or her date of birth or their
nationality. A proper organization of this information demands separate
objects and therefore we must create new entities to represent them.
Additional entities are added to the model in the same way as we did with
the first one. Figure 10-27, below, shows our model with a new entity
called Authors containing an attribute called name.
A relationship only needs two values: its name (the name of the property)
and the destination (the type of objects it is referencing), but it requires
some parameters to be set. We must tell the model if the relationship is
going to be optional, define its type (To-One or To-Many), and determine
what should happen to the destination object if the source object is
deleted (the Delete Rule). All these options are available in the Data Model
Inspector panel when the relationship is selected, as shown below.
By default, the relationship is set as Optional, which means that the source
may be connected to a destination object or not (a book can have an
author or not), the Type of the relationship is set to To-One (a book can
only have one author), and the Delete Rule is set to Nullify. The following
are all the values available for this rule.
IMPORTANT: The Delete Rules are a way to ensure that the objects
remaining in the Object Graph are those that our application and the
user need. But we can always set the rule to Nullify and take care of
deleting all the objects ourselves.
There is a third value for the relationship called Inverse. Once we set the
relationships on both sides, it is highly recommendable to set this value. It
just tells the model what the name of the opposite relationship is. Core
Data needs this to ensure the consistency of the Object Graph. Figure 10-
32 shows the final setup for both relationships.
The creation of the model is just the first step in the definition of the Core
Data system. Once we have all the entities along with their attributes and
relationships set up, we must initialize Core Data. Core Data is created from
a group of objects that are in charge of all the processes required to
manage the data, from the organization of the Object Graph to the storage
of the graph in a database. There is an object that manages the model, an
object that stores the data on file, and an object that intermediates
between this Persistent Store and our own code. The scheme is called
Stack. Figure 10-32 illustrates a common Core Data stack.
The code in our application interacts with the Context to manage the
objects it needs to access, the Context asks the Persistent Store to read or
add new objects to the graph, and the Persistent Store processes the
Object Graph according to the model and saves it in a file.
The Core Data framework offers classes to create objects that represent
every part of the stack. The NSManagedObjectModel class manages the model,
the NSPersistentStore class manages a Persistent Store, the
NSPersistentStoreCoordinator class is used to manage all the Persistent Stores
available (a Core Data stack can have multiple Persistent Stores), and the
NSManagedObjectContext creates and manages the context that intermediates
between our app and the store. Although we can instantiate these objects
and create the stack ourselves, the framework offers the NSPersistentContainer
class that takes care of everything for us. The class includes the following
initializer and properties to access each object of the stack.
loadPersistentStores(completionHandler: Closure)—This
method loads the Persistent Stores and executes a closure when the
process is over. The closure receives two arguments, an
NSPersistentStoreDescription object with the configuration of the stack, and
an optional Error value to report errors.
All the communication between our app and the data in the Persistent
Store is done through the context. The context is created by the container
from the NSManagedObjectContext class. This class includes properties and
methods to manage the context and the objects in the Persistent Store.
The following are the most frequently used.
When working with Core Data, the Core Data's Persistent Store becomes
our app's data model. Therefore, we can define a specific class to initialize
the Core Data stack, or just do it from our model, as in the following
example.
Listing 10-47: Initializing the Core Data stack from the model

import SwiftUI
import CoreData
init() {
container = NSPersistentContainer(name: "books")
container.viewContext.automaticallyMergesChangesFromParent = true
container.loadPersistentStores(completionHandler: { storeDescription, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}

The ApplicationData class in this example defines a property called container to
store a reference to the Persistent Store. When the object is initialized, we
create an instance of the NSPersistentContainer class with the name of the
Core Data model (in our example, we called it "books") and configure it to
merge the changes between the context and the Persistent Store (this is
the configuration recommended by Apple). The object creates the stack
but does not load the Persistent Stores, we have to do it ourselves with the
loadPersistentStores() method. After completion, this method executes a
closure with two values: a reference to the Persistent Store just created,
and an Error value to report errors. Errors are infrequent, but if one occurs,
we should warn the user. For instance, we can modify a state property to
open an Alert View to report the situation. In this example, we just call the
fatalError() function to stop the execution of the app.
Once we have the container, we must get the context from it and share it
with the views, so they can add, fetch, or remove objects from the
Persistent Store. The environment offers the managedObjectContext property
for this purpose. The following are the modifications we must introduced
to the App structure to inject a reference to the context into the
environment.
@main
struct TestApp: App {
@StateObject var appData = ApplicationData()
Core Data does not store our custom objects; it defines a class called
NSManagedObject for this purpose. Every time we want to store information
in the database, we must create an NSManagedObject object, associate that
object to an Entity, and store the data the entity allows. For example, if we
create an object associated to the Books entity, we are only allowed to
store five values that corresponds to the Entity's attributes and relationship
(title, year, cover, thumbnail, and author). The class includes the following
initializer and methods to create and manage the objects.
NSManagedObject(context: NSManagedObjectContext)—This
initializer creates a new instance of the NSManagedObject class, or a
subclass of it, and adds it to the context specified by the context
argument.
fetchRequest()—This type method generates a fetch request for an
entity. A fetch request is a request we use to fetch objects of a
particular entity from the Persistent Store.
entity()—This type method returns a reference to the entity from
which the managed object was created. It is an object of type
NSEntityDescription with a description of the entity.
To ask Xcode to create the subclasses for us, we must select the entities
one by one, select the Class Definition value for the Codegen option (Figure
10-33, number 2), and make sure that the name of the subclass is specified
in the Name field (Figure 10-33, number 1). Once the options are set, the
classes are automatically created. For example, when we set these options
for the entities in our model, Xcode creates a subclass of NSManagedObject
called Books with the properties title, year, cover, thumbnail, and author, and a
subclass called Authors with the properties name and books. From now on, all
we need to do to store a book in the Persistent Store is to create an
instance of the Books class using the NSManagedObject initializer.
A fetch request loads all the objects available in the Persistent Store. This is
not a problem when the number of objects is not significant. But a
Persistent Store can manage thousands of objects, which can consume
resources that the app and the system need to run. Therefore, instead of
the fetch request, the @FetchRequest property wrapper produces a value of
type FetchedResults. This is a structure that takes care of loading into the
context only the objects that are required by the view at any given
moment. We will learn more about it in the following sections.
Asynchronous Access

It is time to see how all these tools work together to store and retrieve
data from a Persistent Store. We are going to use a project like the one
created before for archiving. The purpose of the application is to show the
list of books stored in the Persistent Store and add new ones.
The initial view lists all the books available in the Persistent Store and
includes a button to open a second view to create new objects with the
values provided by the user.
All the interaction between our code and the Persistent Store is done
through the context. When we want to access the objects already stored,
add new ones, remove them, or modify any of their values, we have to do
it in the context and then move those changes from the context to the
Persistent Store. The @FetchRequest property wrapper automatically gets a
reference of the context from the environment (this is the reference we
injected into the context in the App structure of Listing 10-48), but when
working directly with Core Data, we must get the reference from the
environment with the @Environment property wrapper and the
managedObjectContext key. In this example, we called this property dbContext.
The view includes two TextField views to let the user insert the title of the
book and the year of publication, and a button in the navigation bar to save
the values. When the button is pressed, the code checks if the values are
valid and then calls an asynchronous method to create and store a new
object in the Persistent Store. We moved the code to an asynchronous
method so that we can implement the perform() method provided by the
context and thus ensure that the process is performed in a safe thread,
which is recommended every time we are adding, removing, or modifying
an object.
The process to add a new book begins with the creation of the new object
with the Books() initializer. This not only creates a new object of type Books
but it also adds it to the context specified by the argument (dbContext). The
next step is to assign the values to the object's properties. We assign the
value of the first input field to the title property, the value of the second
input field to the year property, the images bookcover and bookthumbnail
to the cover and thumbnail properties, respectively (we assign standard
images to every book for now), and the nil value to the author property (we
still don't have an Authors object to associate with this book).
The Books() initializer inserts the new object into the context, but this
change is not permanent. If we close the app after the values are assigned
to the properties, the object is lost. To persist the changes, we must save
the context with the save() method. This should be done every time we
finish a process that modifies the context. The method takes the
information in the context and updates the Persistent Store with it, so
everything is stored permanently in the file.
There are no view models in Core Data. The objects stored in the Persistent
Store (Books and Authors in our example), represent the application's data
model. They store the values and return them as they are. But the views
need the values to be formatted or casted before showing them on the
screen. For instance, in the RowBook view in Listing 10-49, we had to
process the value of the thumbnail property with a computed property to
turn it into an UIImage view. We also had to use the nil-coalescing operator
to show a string if there was no value in the title and author properties. And
we even had to cast the value of the year property to a string with the
String() initializer. All this work should not be done by the view, it should be
done by a view model. To create a view model for the Core Data objects,
we can extend the classes defined by the system (see Extensions in Chapter
3). For example, we can create an extension of the Books class to provide
computed properties that always return String values for the views to
display, and process the images in the thumbnail and cover properties, so we
don't have to do it inside the view.
Listing 10-51: Defining a view model for the Core Data objects

import SwiftUI
extension Books {
var showTitle: String {
return title ?? "Undefined"
}
var showYear: String {
return String(year)
}
var showAuthor: String {
return author?.name ?? "Undefined"
}
var showCover: UIImage {
if let data = cover, let image = UIImage(data: data) {
return image
} else {
return UIImage(named: "nopicture")!
}
}
var showThumbnail: UIImage {
if let data = thumbnail, let image = UIImage(data: data) {
return image
} else {
return UIImage(named: "nopicture")!
}
}
}
extension Authors {
var showName: String {
return name ?? "Undefined"
}
}

Extensions have access to the properties defined by the data type. This
means that we can access those properties, process their values, and
return something else. The example in Listing 10-51 defines an extension
for the Books class and an extension for the Authors class. The extensions
include computed properties that format the values in the original
properties and return a value for the views to display. In the extension of
the Books class, we also include a property that returns a string with the
name of the author, so the views don't have to read this value from the
Authors object anymore, they can do it directly from the Books object.
Authors objects are generated and stored the same way as Books objects.
This demands our application to provide new views where the user can
select and add more objects. For our example, we have decided to expand
our interface with a view that lists the authors already inserted by the user
and another view to insert new ones.
The view on the left in Figure 10-35 is the InsertBookView view introduced
before to insert new books. This view now shows three input options, an
input field to insert the title of the book, another to insert the year, and
the Select Author button to select the author. This button is a NavigationLink
view that opens a view to list all the authors available (Figure 10-35,
center). In turn, this view includes a + button in the navigation bar to open
a view that includes an input field to insert the name of an author (Figure
10-35, right). The first step we need to take to create this interface is to
add the Select Author button to the InsertBookView view, as shown below.
This view lists the authors already inserted by the user. The @FetchRequest
property is called listOfAuthors and it is defined to work with Authors objects,
but other than that, the rest of the code is the same we used to list books.
The only significant difference is that the rows now include the
onTapGesture() modifier to let the user select an author. When the user taps
on the name of an author, we assign the Authors object to a @Binding
property called selected and close the view. Because the selected property is
connected to the selectedAuthor property defined in the InsertBookView view,
the name of the author selected by the user will be shown on the screen.
If there are no authors in the Persistent Store yet, or the author the user is
looking for is not on the list, the user can press a button in the navigation
bar that opens the InsertAuthorView view to insert a new author.
This is a simple view. It includes one TextField view to insert the name of the
author and a button to save it. When the button is pressed, we follow the
same procedure as before. The Authors object is initialized and stored in the
context, the name inserted by the user is assigned to the object's name
property, and the context is saved to make the changes permanent.
With these additions, our basic app is complete. When we pressed the
Select Author button, the app opens a view with all the authors available
(Figure 10-35, center). If there are no authors yet or the author we want is
not on the list, we can press the + button to insert a new one (Figure 10-35,
right). Every time we select an author from the list, the app goes back to
the view with the book’s information and shows the name of the selected
author on the screen (Figure 10-35, left). The Authors object that represents
the author is assigned to the book’s author property and therefore the name
of the author is now shown on the list of books.
This model creates the Persistent Store as before, but it also includes a
type property called preview to create a Persistent Store in memory for the
previews. The closure assigned to this property initializes the ApplicationData
object with the value true. The initializer checks this value and assigns a URL
structure with a null URL to the url property of the NSPersistentStoreDescription
object returned by the persistentStoreDescriptions property of the
NSPersistentContainer class. The NSPersistentStoreDescription object is used by Core
Data to create and load the Persistent Store, so when we assign a URL with
the "/dev/null" path to this object, the Persistent Store is created in
memory.
Now that we have a type property to create the Persistent Store for
previews, we can use it in the PreviewProvider structures. All we need to do is
to inject it into the environment as we did in the App structure, but this
time we access the viewContext property of the NSPersistentContainer object
returned by the ApplicationData object created by the preview property, as
shown next.
Listing 10-57: Accessing the Core Data context from the preview

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environment(\.managedObjectContext, ApplicationData.preview.container.viewContext)
}
}

Objects returned from a request are usually in the order they were
created, but this is not guaranteed. Foundation defines two data types to
sort objects, the NSSortDescriptor class to define NSFetchRequest objects, and
the SortDescriptor structure, designed to work with the @FetchRequest property
wrapper. The most useful in SwiftUI applications is the SortDescriptor
structure. From these structure, we can specify an order according to the
values of a property. The structure includes the following initializer.
If later we need to change the order of the objects, we can assign new
SortDescriptor structures to the FetchedResults structure created by the
@FetchRequest property wrapper. For this purpose, the structure includes the
sortDescriptors property. In the following example, we add a button to the
navigation bar that opens a menu with three options to sort the values.
Listing 10-61: Modifying the sorting criteria

struct ContentView: View {
@FetchRequest(sortDescriptors: [SortDescriptor(\Books.title, order: .forward)], predicate: nil,
animation: .default) var listOfBooks: FetchedResults<Books>
The button is created by a Menu view, and the menu includes three Button
views for each option. When a button is pressed, we create a SortDescriptor
structure with the configuration we want and then assign it to the
property of the FetchedResults structure. The first button sorts
sortDescriptors
the books by title (the configuration by default), the second button sorts
the books by author, and the third button by year.

Predicates

The requests performed in previous examples are getting all the objects
associated to a particular entity and the values of all their properties. The
Foundation framework defines a class called NSPredicate to filter collections
of objects. For instance, using this class we could get only the books that
were published in the year 1983 or the authors which names start with
"Stephen". The class defines the following initializer to create a predicate
with all the conditions we need.
Of course, these are predefined conditions, but we can allow the user to
provide the values used to filter the objects. To achieve this, we need to
replace the current predicate with a new one configured with the values
inserted by the user.
To dynamically assign a new predicate to the request, the FetchedResults
structure includes the nspredicate property. In the following example, we
show how to search books by year by assigning a new predicate to this
property every time the user inserts a 4 digits number in the search field.
Listing 10-64: Assigning new predicates to search for values inserted by the
user

struct ContentView: View {
@FetchRequest(sortDescriptors: [], predicate: nil, animation: .default) var listOfBooks:
FetchedResults<Books>
@State private var search: String = ""
Now, when a book is selected, the app opens the AuthorBooksView view with
a reference to the Authors object that represents the author of the book. In
this view, we use that reference to create a new fetch request as soon as
the view appears on the screen.
init(selectedAuthor: Authors?) {
self.selectedAuthor = selectedAuthor
if selectedAuthor != nil {
_listOfBooks = FetchRequest(sortDescriptors: [SortDescriptor(\Books.title, order:
.forward)], predicate: NSPredicate(format: "author = %@", selectedAuthor!), animation:
.default)
}
}
var body: some View {
List {
ForEach(listOfBooks) { book in
Text(book.title ?? "Undefined")
}
}
.navigationBarTitle(selectedAuthor?.name ?? "Undefined")
}
}
struct AuthorBooksView_Previews: PreviewProvider {
static var previews: some View {
AuthorBooksView(selectedAuthor: nil)
.environment(\.managedObjectContext, ApplicationData.preview.container.viewContext)
}
}

This view receives a reference to the Authors object that represents the
author selected by the user, but we cannot use this value in the
@FetchRequest property wrapper because the property is not available until
the whole structure is initialized. We must define a basic request with a
false predicate (nothing is loaded at first) and then assign a new FetchRequest
structure to the property wrapper from the initializer with the predicate
we need to only get the books published by the selected author.
As explained in Chapter 6 (see Listing 6-7), to access the underlining
structure of the property wrapper, we must precede the property name
with an underscore (_listOfBooks). In this example, we initialize the
property, and then assign a new FetchRequest structure to the
selectedAuthor
property wrapper with a predicate that filters the books by the author
assigned to that property. The result is shown below.
Do It Yourself: Update the List view in the ContentView view with the
code in Listing 10-67. Create a SwiftUI View file called
AuthorBooksView.swift for the view in Listing 10-68. Run the
application on the iPhone simulator. Select a book. You should see
the titles of all the books published by the selected author on the
screen.
Modifying Objects

In this example, we apply the id() modifier to the RowBook view. We have
introduced this modifier before; it assigns a unique identifier to each view.
In this case, we use a new UUID value, which means that every time the
view is recreated, the identifier will be different. This makes the system
believe that this is a different view and therefore it updates the content,
showing on the screen the new values inserted by the user.
The NavigationLink view opens a view called ModifyBookView to allow the user
to modify the values of the selected book. The following is our
implementation of this view.
This view defines a constant called book to receive the Books object that
represents the book selected by the user. Because the user needs to see
the book to be able to modify it, we implement the onAppear() modifier to
initialize state properties with the book's values. Notice that there is an
additional property called valuesLoaded that we check to only load the values
when the property is false. This is to make sure that the values are only
loaded when the user selected a book from the ContentView view, but not
when a new author has been selected from the AuthorsView view.
The view includes two TextField views for the user to be able to modify the
title and the year, and the same Select Author button included before to
open the AuthorsView view to select an author. When the Save button is
pressed, the values inserted by the user are assigned to the object's
properties and the context is saved.
In this case, we do not create a new Books object, we just modify the
properties of the Books object received by the view. The result is shown
below.
Do It Yourself: Update the List view in the ContentView view with the
code in Listing 10-69. Create a SwiftUI View file called
ModifyBookView.swift for the view in Listing 10-70. Run the
application on the iPhone simulator. Tap on a book to select it.
Change the title and press Save. You should see the changes on the
list.
Deleting Objects

In our example, the objects retrieved from the Persistent Store are stored
in the listOfBooks property. The indexes received by the closure in the
onDelete() modifier are the indexes of the objects the user wants to delete
from this collection. Therefore, to remove the objects selected by the user,
we iterate through the indexes of all the objects to be removed with a for in
loop, get the objects from the listOfBooks collection using the indexes, and
remove them from the context with the delete() method. Once the objects
are removed from the context, the listOfBooks property is automatically
updated and the changes are reflected on the screen. The last step is to
save the context with the save() method to persist the changes in the
Persistent Store, as we did before. (Notice that in this example we had to
get a direct reference to the context from the environment with the
@Environment property wrapper to be able to call the delete() method on it.)
So far, we have let the @FetchRequest property wrapper create the request
for us, but there are situations in which we must create our own requests
to process the values in the Persistent Store. Because a request has to be
associated to an entity, subclasses of the NSManagedObject class, like Books
and Authors, include the fetchRequest() method. This method returns an
NSFetchRequest object with a fetch request associated to the entity
represented by the class. To perform this request, the context includes the
fetch() method.
The following example creates a request when the view appears on the
screen and when an object is removed to count the number of books in the
Persistent Store and show the value at the top of the list.
ForEach(listOfBooks) { book in
NavigationLink(destination: ModifyBookView(book: book), label: {
RowBook(book: book)
.id(UUID())
})
}
.onDelete(perform: { indexes in
for index in indexes {
dbContext.delete(listOfBooks[index])
countBooks()
}
do {
try dbContext.save()
} catch {
print("Error deleting objects")
}
})
}
.navigationBarTitle("Books")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: InsertBookView(), label: {
Image(systemName: "plus")
})
}
}
.onAppear {
countBooks()
}
}
}
func countBooks() {
let request: NSFetchRequest<Books> = Books.fetchRequest()
if let list = try? self.dbContext.fetch(request) {
totalBooks = list.count
}
}
}

To store the number of books, this view defines a @State property called
totalBooks, and to show the value, it includes an HStack view with two Text
views on top of the list. The request is created by a method called
countBooks(). The method is executed when the view appears and when an
object is removed. It creates a request for the Books entity, and then
executes the request in the context with the fetch() method. If there are no
errors, this method returns an array with all the objects that match the
request. In this case, we didn't define any predicate, so the array contains
all the Books objects in the Persistent Store. Finally, we count the objects
with the count property and assign the value to the totalBooks property to
show it to the user.
This view includes a Text view that shows the total number of books next to
the author's name. To get this value, we count the number of items in the
books property or show the value 0 if the property is equal to nil (no books
have been assigned to the author).
SectionedFetchRequest(sectionIdentifier: KeyPath,
sortDescriptors: [SortDescriptor], predicate: NSPredicate?,
animation: Animation?)—This initializer creates a request with the
configuration determined by the arguments and produces a
SectionedFetchResults structure that manages and delivers the objects to
the view. The sectionIdentifier argument is a key path to the property
that the request is going to use to create the sections, the
sortDescriptors argument is an array of SortDescriptor structures that
determine the order of the objects, the predicate argument is an
NSPredicate object that filters the objects, and the animation argument
determines how the changes are going to be animated.
SectionedFetchRequest(fetchRequest: NSFetchRequest,
sectionIdentifier: KeyPath, animation: Animation?)—This
initializer creates a request with the NSFetchRequest object provided by
the fetchRequest argument and produces a SectionedFetchResults
structure that manages and delivers the objects to the view. The
sectionIdentifier argument is a key path to the property that the
request is going to use to create the sections, and the animation
argument determines how the changes are going to be animated.
The @SectionedFetchRequest property wrapper produces a value of type
SectionedFetchResults. This is a generic structure that works with two data
types: the data type of the property used to identify the sections and the
data type of the objects we are fetching.
The sections are created from the Section structure, defined by the
SectionedFetchResults structure. The Section structure includes the following
properties to describe the section.
In this example, we use the name property of the Authors entity to identify
the sections, so we get one section per author. Because a book may not
have an associated author, the author property of the Books object may
return nil, and therefore the value used to identify the section is an
optional String. That's the reason why we declare the SectionedFetchResults
data types as <String?, Books>.
The sections are created as before (see Chapter 7, Listing 7-21). We need
to define a ForEach loop for the sections, and then another ForEach loop to
list the objects in each section. The sections are identified with a Section
view, and the section's label is created with a Text view from the value
returned by the id property. (In our example, this is the author's name.)

Do It Yourself: Update the ContentView view with the code in Listing
10-76. Run the application on the iPhone simulator. You should see
the books organized by author into sections, as shown in Figure 10-
41.
do {
try dbContext.save()
dismiss()
} catch {
print("Error saving record")
}
}
}

When the user presses the Save button to save the book, we read the first
property to get the letter in the string and make sure that is an uppercase
letter. If the letter is a number, we replace it with a # character. (All books
which titles begin with a number will be listed in a unique section
identified with the # character.) Finally, the letter is stored in the firstLetter
property and the book is saved.
We must perform the same process when a book is modified. The
following are the changes we need to introduce to the saveBook() method in
the ModifyBookView view.
do {
try dbContext.save()
dismiss()
} catch {
print("Error saving record")
}
}
}

Now that every book knows the letter it belongs to, we can list them in
alphabetical sections. The following is the new @SectionedFetchRequest
property wrapper we need for the ContentView view.
Now, the section identifier is the value of the firstLetter property and the
books are sorted by title.
The previous examples assumed that there was only one author per book,
but sometimes multiple authors collaborate to write a book. To assign
multiple Authors objects to a book, we must turn the author relationship of
the Books entity into a To-Many relationship, as shown below.
Now, both relationships are of type To-Many, which means that we can
assign multiple books to an author and multiple authors to a book. This
introduces a problem. Before, every time we wanted to assign an author to
a book, we just had to create a new Authors object and assign it to the
book's author property. Core Data took care of adding the book to the books
property of the Authors object, along with the rest of the books associated
to that author. But we cannot do that anymore when both relationships
are To-Many. In that case, we must read and write the values ourselves.
The values of a To-Many relationship are stored in an NSSet object. This is a
class defined by the Foundation framework to store sets of values. To read
the values in an NSSet, we can cast it as a Swift set, but to turn a Swift set or
an array into an NSSet object, we must implement the following initializers.
In our application, the first place we need to read these values is in the
view model (the Extensions.swift file). We must create a property that
returns a string with the list of authors separated by comma.
Listing 10-80: Creating a string with the name of the authors from the view
model

import SwiftUI
extension Books {
var showTitle: String {
return title ?? "Undefined"
}
var showYear: String {
return String(year)
}
var showAuthors: String {
var authors: String!
if let list = author as? Set<Authors> {
let listNames = list.map({ $0.name ?? "Undefined" })
if !listNames.isEmpty {
authors = listNames.joined(separator: ", ")
}
}
return authors ?? "Undefined"
}
var showCover: UIImage {
if let data = cover, let image = UIImage(data: data) {
return image
} else {
return UIImage(named: "nopicture")!
}
}
var showThumbnail: UIImage {
if let data = thumbnail, let image = UIImage(data: data) {
return image
} else {
return UIImage(named: "nopicture")!
}
}
}
extension Authors {
var showName: String {
return name ?? "Undefined"
}
}

The showAuthors property in this view model replaces the showAuthor property
implemented before. To create the list of names, we cast the value of the
author property to a Set<Authors> value. This creates a Swift set with Authors
objects representing all the authors assigned to the book, so we can map
the values into an array of strings and call the joined() method to create a
single string with the names separated by comma.
Now, we can show all the authors of a book on the list by reading the
showAuthors property, as shown next.
The next step is to allow the users to associate multiple authors with a
book. The following are the changes we must introduce to the
InsertBookView view to allow the user to select multiple authors when a new
book is added.
do {
try dbContext.save()
dismiss()
} catch {
print("Error saving record")
}
}
}
}

The values are now stored in an array, so we always know which ones were
selected by the user. To show the list of authors, we define a computed
property called showAuthors that follows the same process performed by the
view model; it maps the Authors objects and returns a string with the names
separated by comma. The view includes a Text view that reads this property
and shows the names on the screen.
When the user decides to save the book, we perform the inverse
procedure. The values in the selectedAuthors array are stored in an NSSet
object and assigned to the author property.
To allow the user to select the authors when a book is modified, we must
also apply these changes to the ModifyBookView view, as shown next.
do {
try dbContext.save()
dismiss()
} catch {
print("Error saving record")
}
}
}
}

This view is very similar to the InsertBookView view, the only difference is a
new Boolean @State property called changesAdded that we use to know if the
user is coming from the ContentView view or the AuthorsView view. If the user
opened this view from the ContentView view, we need to load the list of
authors from the Persistent Store and assign them to the selectedAuthors
property when the view appears. Otherwise, the content of the
selectedAuthors property is determined by the authors selected by the user in
the AuthorsView view. Next are the changes we need to introduce to this
view.
As always, this view displays all available authors. To show which one has
been previously selected, we add an Image view with a checkmark to the
row of the authors that are already in the selectedAuthors array. Then, when a
row is tapped by the user, we check whether the author was previously
selected or not. If it was selected, we remove it from the selectedAuthors
array, otherwise, we add it to it. This makes sure that the array only
contains the authors currently selected by the user.
Do It Yourself: Open the Core Data model. Select the Books entity
and change the Type of the author relationship to To-Many (Figure
10-44). Update the Extensions.swift file with the code in Listing 10-
80, the ContentView view with the code in Listing 10-81, the
InsertBookView with the code in Listing 10-82, the ModifyBookView view
with the code in Listing 10-83, and the AuthorsView view with the code
in Listing 10-84. Run the application on the iPhone simulator. Press
the + button to add a new book. Press the Select Authors button to
select an author. You should be able to add as many authors as you
want.
The To-Many to To-Many relationships also change the way we search for
values. For instance, we cannot search for a book by author as we did
before because now a book may be associated to many authors. Instead,
we must tell the predicate to search for the value inside the set of authors.
For this purpose, predicates can include the following keywords.
ANY—This keyword returns true when the condition is true for some
of the values in the set.
ALL—This keyword returns true when the condition is true for all the
values in the set.
NONE—This keyword returns true when the condition is false for all
the values in the set.
This example defines an onAppear() modifier that we can add to a view in the
ContentView view. It creates a request that finds all the books associated with
an author named "Stephen King". The predicate reads all the Authors
objects in the relationship and returns the book when one of the names
matches the string.
Do It Yourself: Add the modifier of Listing 10-85 to the List view in
the ContentView view. You also need to get access to the Core Data
context with the @Environment property wrapper
(@Environment(\.managedObjectContext) var dbContext). Run the application
on the iPhone simulator and insert a few books with the author
Stephen King. You should see the names of the books associated
with that author printed on the console.
The example we have been working on so far turns the NSSet object
returned by the author relationship into an array of Authors objects and
then adds or removes authors from this array, but if we need to add or
remove values directly from the NSSet object, we must turn it into an
NSMutableSet object. This class creates a mutable set and therefore it allows
us to add or remove values from it. To create an NSMutableSet object from an
NSSet object, the NSManagedObject class includes the following method.
The NSMutableSet class includes the following methods to add and remove
items in the set.
Task(priority: .high) {
await dbContext.perform {
for book in listOfBooks {
let authorSet = book.mutableSetValue(forKey: "author")
authorSet.remove(author)
book.author = authorSet
}
try? dbContext.save()
}
}
}
}

This example updates the onAppear() modifier defined before to modify the
Books objects in the Persistent Store as soon as the view is loaded. First, we
perform a request to get the Authors object with the name "Stephen King".
Then, we use a for in loop to modify the books loaded by the @FetchRequest
property wrapper. In the loop, we turn the NSSet object returned by the
author relationship into an NSMutableSet object, remove from the set the
Authors object fetched before with the remove() method, and assign the
result back to the author relationship, effectively removing that author
from every book.
As with many other views, if no size is specified, graphic views take the size
of their container, but we can declare a specific size with the frame()
modifier. The following example shows all the standard shapes available.
We included the views in a horizontal ScrollView to allow the list to scroll.
Listing 11-1: Drawing standard shapes

struct ContentView: View {
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: true) {
HStack {
Rectangle()
.frame(width: 100, height: 100)
RoundedRectangle(cornerRadius: 25, style: .continuous)
.frame(width: 100, height: 100)
Circle()
.frame(width: 100, height: 100)
Ellipse()
.frame(width: 100, height: 50)
Capsule()
.frame(width: 100, height: 50)
}.padding()
}
Spacer()
}
}
}

Adding a border requires a similar process, but there are two types of
modifiers and they produce a slightly different result. The stroke() modifier
expands the border outward and inward, while the strokeBorder() modifier
generates an inner border.
Listing 11-3: Defining a border

struct ContentView: View {
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 25)
.stroke(Color.red, lineWidth: 20)
.frame(width: 100, height: 100)
.padding()
RoundedRectangle(cornerRadius: 25)
.strokeBorder(Color.red, lineWidth: 20)
.frame(width: 100, height: 100)
.padding()
}
}
}

These two modifiers can also take a StrokeStyle structure to fine-tune the
border. The structure provides the following initializer.
Shapes are views and therefore they can be combined with other SwiftUI
views and controls. For instance, the following example assigns a Capsule
shape as the background of a button.
Using this modifier, we can declare the background of the button in the
previous example with a single line of code.
The button toggles the value of a @State property. If the value is true, we
show the label "Active" and assign a green capsule to the button's
background, otherwise, we display the label "Inactive" and turn the
capsule red.

Gradients

The filling and border of a shape can also be defined with gradients.
SwiftUI includes four structures designed to present gradients:
LinearGradient, RadialGradient, AngularGradient, and EllipticalGradient. These
structures conform to the ShapeStyle protocol, which defines the following
methods to create customized instances.
The code in Listing 11-7 defines a gradient with two colors, red and green,
and then applies the gradient to a RoundedRectangle view with the structure
returned by the linearGradient() method. Because we declare the value bottom
as the starting point and the value top as the ending point, the colors are
displayed from bottom to top in the order declared by the Gradient
structure.

When a gradient is created without specifying color stops, the colors are
evenly distributed throughout the area occupied by the gradient. If we
want to customize the distribution, we must define the colors for the
gradient with Stop structures.
The following example reproduces the previous gradient, but this time the
green color begins at the position 0.4 (40% of the area occupied by the
gradient).

Besides the linear gradient, we can also create gradients with different
shapes. For instance, radial and elliptical gradients are created with circular
layers drawn from the center of a circle outwards, as shown next.

Another type of gradients we can use for our shapes are the angular or
conic gradients. These gradients draw the colors around a circle, which
makes it look like a cone seen from the top. The values required depend on
the type of cone we want to define. For a simple cone, all we need is the
Gradient structure, the center of the circle, and the angle where the gradient
begins.
The angles for the gradient are declared with an instance of the Angle
structure. This structure includes two type methods to defined the value in
degrees or radians: degrees(Double) and radians(Double). In the example of
Listing 11-10, we declare the beginning of the gradient at an angle of 180
degrees, which is the opposite side of the default starting point.

Effects

The ShapeStyle protocol that defines the type methods to create gradient
structures implemented in the previous section also defines multiple
properties and methods to apply other effects to a view. The following are
the most frequently used.

Patterns

Besides colors and gradients, we can also use images to fill a shape. SwiftUI
includes the ImagePaint structure for this purpose. The structure includes the
following type method to create a customized instance.
By default, the ImagePaint structure uses the whole image in the original
scale, so most of the time specifying the image is enough for the system to
create the pattern, as in the following example.
The image repeats indefinitely to fill the entire shape. In this example, we
define a square of 100 by 100 points and then paint it with an image of a
size of 25 by 25 points. Because the image is smaller than the shape, it is
drawn multiple times to cover the area.
Figure 11-11: Pattern

11.2 Paths

The shapes we have implemented so far are defined by paths. A path is a
set of instructions that determine the outline of a 2D shape. In addition to
the paths defined by the standard shapes introduced before, we can create
our own. For this purpose, SwiftUI includes the Path view.
Path View

The Path view is designed to create a view that contains a custom path. The
following are some of the initializers.
The path is created with a combination of lines and curves. The strokes
move from one point to another in the view's coordinates, as if following
the movement of a pencil. The Path structure defines a set of modifiers to
determine the position of the pencil and generate the path. The following
are the most frequently used.
To create a path, we must apply the modifiers in order, following the line of
an imaginary pencil. The following example creates a path with the shape
of a triangle.
Because arcs are calculated from the coordinates of the center of the circle
and its radius, we must consider these two values to connect the arc with
the previous line. If the initial coordinates of the arc do not coincide with
the current position of the pencil, a line is created between these two
points to connect the path. Figure 11-13, below, shows the path we get
with the example of Listing 11-14 and what we see if we move up the
center of the arc by 10 points (y: 160).
The addRect() and addEllipse() modifiers allow us to add rectangles and circles
to the path. The modifiers add the shapes to the current path, but they
move the pencil to the position indicated by the CGRect value, so they are
considered independent shapes.
In this case, no line is generated between the current position of the pencil
and the ellipse if they are not connected. Figure 11-14, below, shows the
path we get with the example of Listing 11-15 and what we will see if we
move the area of the circle 10 points to the right (x: 210).
To create a quadratic curve, we move the pencil to the point 50, 50, finish
the curve at the point 50, 200, and set the control point at the position
100, 125.
The cubic curve generated by the addCurve() modifier is more complicated.
There are two control points for this curve, the first one at the position
200, 125, and the second one at the position 300, 125. These points shape
the curve, as shown below.
The paths we have created so far use fixed values. This means that the
shape is always going to be of the same size, no matter the size of the view.
To adapt the path to the size of the view, we must calculate how much
space is available with the GeometryReader view (see Chapter 6).
Listing 11-17: Adapting the size of the path to the size of the container

struct ContentView: View {
var body: some View {
GeometryReader { geometry in
Path { path in
let width = geometry.size.width / 2
let height = width
let posX = (geometry.size.width - width) / 2
let posY = (geometry.size.height - height) / 2
The code in Listing 11-17 draws a triangle that is always half the width of
its container. For this purpose, we first calculate the triangle's width
dividing the width of the geometry by 2. Then, we assign this value to the
height constant to set the height equal to the width. After the dimensions
are calculated, we determine the position of the initial point. Because we
want to center the triangle in the container, we get the remaining space by
subtracting the triangle's width from the width of the geometry and then
divide the result by 2 to get the initial point. We do the same for the
vertical position and store the values in the posX and posY constants. With
these values, we can finally draw the path. The move() modifier moves the
pencil to the initial position determined by posX and posY. Next, the addLine()
modifier draws a line from this point to the point located at the right end
of the triangle (posX + width). The next addLine() modifier draws a line from
this point to the point at the bottom left of the triangle (posY + height). And
finally, the closeSubpath() modifier draws the vertical line to close the path.
Because we calculate all the coordinates of the path from the values of the
geometry, the triangle adapts to the size of its container and is always half
the container's size and centered in the view, no matter the device or the
size of the screen.

Custom Shapes

return path
}
}

The path is the same as previous examples, but now we take the values
from the CGRect structure received by the method to calculate the size of
the shape. In this case, we extend the triangle from left to right and top to
bottom to cover the whole view. (The size of the triangle matches the
view's width and height, which is the recommended approach for custom
shapes.)
Once the Shape view is defined, we can implement it in our interface as any
other view. To illustrate how this works, we can instantiate multiple Triangle
views of different sizes within a horizontal ScrollView.
When a Triangle view is created, the path() method is called with the
dimensions of the view and the triangle is drawn according to those values.
Therefore, if we define Triangle views of different sizes, we get triangles of
different sizes and shapes on the screen.
These modifiers affect the content of the view. For instance, if we apply an
offset to an Image view, the image inside the view is displaced the distance
determined by the modifier, but the view's frame is not affected.
The code in Listing 11-20 displaces the image 75 points to the right (half
the width of the Image view).
The rotation modifiers work in a similar way. They rotate the content of the
view in 2D or 3D. The most interesting is the rotation3DEffect() which can
rotate the content on any axis.
These modifiers are implemented by Shape views and therefore they must
be applied before other modifiers, as in the following example.
Again, the transformation modifiers affect the content of the view, in this
case the shape, but the view itself remains the same. In the example of
Listing 11-23, we create a RoundedRectangle view and rotate it 45 degrees.
The scale() modifier is not only used to resize the shape but also to achieve
cool effects. For instance, we can contract or expand shapes by declaring
different values for the horizontal and vertical scales, or create a mirror
image by declaring a negative value. The following example implements
this trick to invert the coordinate system and draw an inverted shape.
This example implements the Triangle view defined in Listing 11-18. The first
instance is displayed with a regular scale, but the second instance is
transformed with the scale() modifier and a horizontal scale of -1, which
inverts the coordinate system, creating a mirror image.
This example creates a Triangle view, but trims the path at the point 0.70,
which represent the 70% of the drawing. This allows the system to draw
the first and second lines in full, but the process is interrupted, and
therefore the triangle is never finished.

11.4 Canvas

With standard and custom shapes, we can add as many graphics to the
interface as needed, but performance drops when too many views are
required. To overcome these limitations, SwiftUI includes the Canvas view. A
view specifically designed for dynamic 2D drawing.
This example creates a Canvas view of the size of the screen. (Notice the
ignoresSafeArea() modifier at the end.) The draw() method takes a CGRect value
and draws the image on that area. In this case, we have decided to use the
whole canvas, so we define a CGRect value with the origin 0,0 and the size
of the canvas, but we can provide a specific size. For instance, the image
we use in this example is 644 pixels wide by 864 pixels tall. From these
values, we can define the CGRect necessary to present the image in a
smaller size.
In this example, we define an area a quarter the size of the original image
and divide the remaining horizontal space by 2 to determine its position.
As a result, the image is centered on the canvas.
Of course, we can also draw shapes, including standard and custom shapes
and paths. For instance, we can combine our image with some graphic and
text.
Listing 11-28: Drawing shapes and text

struct ContentView: View {
var body: some View {
Canvas { context, size in
let imageFrame = CGRect(x: 60, y: 75, width: 215, height: 288)
context.draw(Image("spot1"), in: imageFrame)
These methods work like those implemented before for shapes, but they
only affect the matrix the context uses to calculate the position and size of
the graphics. For instance, if we call the rotate() method on the context, the
graphics already on the canvas are unchanged, only the graphics drawn
afterwards are rotated. For an example, we can draw two copies of the
same image on the canvas. The first one is drawn on the standard canvas
and the second one is drawn after the canvas is rotated 20 degrees.
context.rotate(by: .degrees(20))
context.draw(Image("spot1"), in: imageFrame)
}.ignoresSafeArea()
}
}

As illustrated by the picture below, only the image that has been drawn
after the rotate() method was applied to the context is affected by the
rotation, the previous image remains the same. This is because the
transformation methods affect the matrix by which the context calculates
how to draw the graphics, not the canvas itself.
The code in Listing 11-30 translates the origin to the center of the canvas,
rotates the canvas 45 degrees, and then calculates the position and size of
the image. In this example, we are showing the image at a quarter of its
original size, as before, but the position is estimated in relation to the new
origin. Because the image's origin is at the position 0, 0 (top-left corner)
and we want to rotate the image around its center, we must specify values
that place the center of the image at the origin of the canvas, and that
means using negative values. The horizontal position is at minus half the
width of the image (-85), and the vertical position at minus half the height
(-108), so the center of the image coincides with the origin of the canvas.
In this code, we create a loop with 10 cycles (from 0 to 9). Each cycle
rotates the context 36 degrees and then draws an image. Because the
rotations are cumulative, the rotation of each image adds to the previous
rotation, forming a complete circle.
Drawing the same image several times affects performance. The system
must prepare the image every time it is about to be drawn. If we need to
improve performance, we can prepare the image beforehand with the
following method.
This method can also prepare Text views and Shading values, but it is
particularly useful with images. The following is the previous example, but
the image is resolved before drawing.
The following code applies a clipping mask with the shape of a circle to the
canvas of the previous example, so only the parts of the images that fall
inside the circle are drawn.
for _ in 0..<10 {
context.rotate(by: .degrees(36))
context.draw(Image("spot1"), in: imageFrame)
}
}.ignoresSafeArea()
}
}


11.5 Charts

In addition to the tools provided by SwiftUI to create and display graphics
on the screen, there is also a framework called Swift Charts that we can
use to produce a graphical representation of the user's data. The following
is the structure provided by the framework to create a chart.
There are several predefined charts available. We can create a bar chart,
line chart, point chart, and more. The graphics used to create these charts
are called Marks. The following are the views included in the framework to
create these marks.
A chart can take quantitative values, like integers, nominal values, like
strings, and temporal values, like dates. Therefore, we can usually create
charts from our app's data without any changes. For instance, we can use a
model from previous examples that includes food items and their calorie
content.
Listing 11-36: Providing the data for a chart

import SwiftUI
init() {
listOfItems = [
Consumables(name: "Bagels", category: "Baked", calories: 250),
Consumables(name: "Brownies", category: "Baked", calories: 466),
Consumables(name: "Butter", category: "Dairy", calories: 717),
Consumables(name: "Cheese", category: "Dairy", calories: 402),
Consumables(name: "Cookies", category: "Baked", calories: 502),
Consumables(name: "Donuts", category: "Baked", calories: 452),
Consumables(name: "Granola", category: "Baked", calories: 471)
]
}
}

The syntax of the Chart view is similar to the List view. We can provide the
data in the view's initializer or use a ForEach loop and then build the views
with the value received by the closure, as shown next.

If we want to create a different type of chart, all we need to do is to
replace the BarMark view by the view we want. For instance, we can
represent the same data with lines.
This chart generates a line between the points in the plot area determined
by the positions assigned to the names in the x axis and the calories in the
y axis. If we only want to show the point, we can implement the PointMark
view to create the graphics. The RectangleMark view represents the values
with rectangles, and the AreaMark view fills the area below or between the
points.
For example, if we know that all the food stored in our model have a
maximum of 1000 calories, we can set a permanent scale between 0 and
1000.
In addition to the graphics that represent the data, the framework defines
views to add other graphics, text, and overlays on top of the chart. The
most frequently used is the RuleMark view, used to draw a line on the plot
area. The following are the view's initializers.
The framework also includes modifiers to configure the view. The following
are the most frequently used.
The line generated by the RuleMark view belongs to the chart but it is
independent of the rest of the graphics, so we should create the chart with
a ForEach view and define the line on top or below the loop, as shown next.
In this example, the Chart view creates the chart, but the marks are
generated by a ForEach loop. Below these loop, we define a RuleMark view to
draw a horizontal line over the chart. (To draw the line below the chart, we
must declare the RuleMark view on top of the ForEach view.) To calculate the
line's vertical position, we define a method called averageCalories() that
returns the average number of calories of all the items in the model. We
also configure the line with a thickness of 5 points and an annotation at the
bottom that reads "Average Calories".
There are also modifiers to configure the chart. The following are the most
frequently used.
Some of these modifiers are useful when using multiple marks to represent
the values. For instance, if we want to use lines and points, we can specify
a larger size for the symbols that represent the points to make them more
visible.
Charts can also represent multiple series of values. The process is similar
but we may have to organize the data in the model according to what we
want to achieve. The following example shows a possible implementation.
In this model, we have a structure to store the date and the amount of
items sold (Sales), and another to store the sales per item (Products). To test
the application, we initialize it with two products, Bagels and Brownies,
and a week of sales each.
init() {
let salesBagels = [
Sales(date: Date(timeInterval: -86400 * 7, since: Date()), amount: 10),
Sales(date: Date(timeInterval: -86400 * 6, since: Date()), amount: 12),
Sales(date: Date(timeInterval: -86400 * 5, since: Date()), amount: 8),
Sales(date: Date(timeInterval: -86400 * 4, since: Date()), amount: 13),
Sales(date: Date(timeInterval: -86400 * 3, since: Date()), amount: 9),
Sales(date: Date(timeInterval: -86400 * 2, since: Date()), amount: 7),
Sales(date: Date(timeInterval: -86400 * 1, since: Date()), amount: 8) ]
let salesBrownies = [
Sales(date: Date(timeInterval: -86400 * 7, since: Date()), amount: 3),
Sales(date: Date(timeInterval: -86400 * 6, since: Date()), amount: 5),
Sales(date: Date(timeInterval: -86400 * 5, since: Date()), amount: 2),
Sales(date: Date(timeInterval: -86400 * 4, since: Date()), amount: 8),
Sales(date: Date(timeInterval: -86400 * 3, since: Date()), amount: 6),
Sales(date: Date(timeInterval: -86400 * 2, since: Date()), amount: 5),
Sales(date: Date(timeInterval: -86400 * 1, since: Date()), amount: 9) ]
sales = [
Products(name: "Bagels", sales: salesBagels),
Products(name: "Brownies", sales: salesBrownies) ]
}
}

To create the chart, we must iterate through the items and then through
the sales for each item, so we need two ForEach loops.
This example creates a line chart. The first ForEach loop gets the products
from the sales property in the model and then reads the sales property of
each product to visualize the sales. Because we are using dates to define
the x axis, we implement the value() method that allows us to specify the
date and the unit we want to use to represent the value (day). As a result,
we get two line charts, one for Bagels and another for Brownies.
In a line chart, the series of values are independent, but bar and area
charts add the marks on top of each other to display the total per value, as
shown in the following example.
In this chart, we replace the LineMark view with a BarMark view, but we also
implement the chartForegroundStyleScale() modifier to assign custom colors to
the bars. The modifier takes a dictionary. The keys are the values used by
the foregroundStyle() modifier to identify each series of values, and the values
are the styles we want to assign to the bars. (In this case, the color red for
Bagels and orange for Brownies).
By default, the bars are added up to show the total, but the framework
offers the following modifier to customize the position.
position(by: PlottableValue, axis: Axis?, span:
MarkDimension)—This modifier assigns a position for the bars. The
by argument determines the value used to identify the bars. The axis
argument specifies the axis used to position the bars. It is an
enumeration with the values horizontal and vertical. And the span
argument determines the space available for the bars. It is a structure
with the type methods fixed(CGFloat), inset(CGFloat), and ratio(CGFloat).
The image is produced by the ImageRenderer object and then returned by its
properties. There are three properties available: uiImage returns a UIImage
object (UIKit), cgImage returns a CGImage object (Core Graphics), and nsImage
returns an NSImage object (Appkit).
To turn a view into an image, we need to store the view in a property so we
can use that property to reference the view from the ImageRenderer's
initializer. For instance, the following example defines a separate view
called NewPictureView with an Image view that presents a circular image. The
view is stored in a property called newPicture that we use to display it on the
screen and then convert it to a UIImage object.
The interface includes a button. When the button is pressed, we create the
ImageRenderer object from the view in the newPicture property and read the
uiImage property to get the UIImage object with the new image. Once we
have this object, we can process it as any other. In this example, we reduce
its size to 25 by 25 points with the preparingThumbnail() method and assign it
as the pattern of a Rectangle view, but we could have save it in a file or a
database. The result is shown below.
This function applies the animation to the states, but the animation is
determined by an instance of the Animation structure. The structure
provides properties and methods to create and configure the animation,
including the following type properties to define standard animations.
This view defines a @State property of type CGFloat with the value 1, uses
this value to define the scale of a Rectangle view with the scaleEffect() modifier,
and provides a button to modify it. When the button is pressed, we assign
the value 2 to the property, expanding the view, but because the
assignment is done within an animation closure, the change is animated.
The animations created by type properties like default and linear are
configured with values by default, but the Animation structure offers
methods to customize the animation.
delay(Double)—This method sets the seconds the animator waits
before starting the animation.
repeatCount(Int, autoreverses: Bool)—This method sets the
number of times the animator performs an animation. The first
argument determines the number of animations to be performed,
and the autoreverses argument determines if the process of going
back to the initial state is going to be animated as well (true by
default).
repeatForever(autoreverses: Bool)—This method determines if
the animator is going to perform the animation indefinitely. The
autoreverses argument determines if the process of going back to the
initial state is going to be animated as well (true by default).
speed(Double)—This method sets the speed of the animation (1 by
default).
Do It Yourself: Update the Button view in the ContentView view with the
code in Listing 11-50. Press the Animate button. You should see the
square animate two times forward and one time backward.
These custom animations are created like the standard animations, and
they can even take the same methods for configuration. For instance, we
can create a customized spring animation (an animation that bounces the
view back and forth) and set its speed and cycle.
Do It Yourself: Update the Button view in the ContentView view with the
code in Listing 11-51. Press the Animate button. You should see the
square bounce indefinitely. Try different values for the
interpolatingSpring() animation and implement other types of
animations to see how they work.
This example defines two @State properties, one to control the scale of the
box and another to change the radius of the corners. When the button is
pressed, we change the values of these two properties, and both changes
are animated.
The system knows how to animate the views because they conform to a
protocol included in SwiftUI called Animatable. The protocol defines a
computed property called animatableData that provides the data required to
create the frames for the animation. Every time we animate a view, the
system gets the value from this property, increases or decreases it by a
small amount, redraws the view, and repeats the process until the value
matches the new one. For instance, the view returned by the opacity()
modifier includes an animatableData property that provides the view's opacity
value, so the system can gradually increase or decrease this value and
redraw the view each time with a different opacity to make it look like it's
being animated.
Most SwiftUI views include the animatableData property and therefore the
system knows how to animate them, but if we want to animate a path in a
custom Shape view, we must do it ourself. We need to make the structure
conform to the Animatable protocol and implement the animatableData
property to tell the system what is the value that it has to modify to get all
the frames for the animation.
The Shape protocol already inherits from the Animatable protocol, so all we
need to do is to implement the animatableData property to provide the value
to animate. This value has to be something we can use to draw the path.
For instance, if we want to animate a circle, we can provide its radius. If the
animation goes from a radius of 0 to a radius of 10, the view will be able to
draw the intermediate values required to create the animation (1, 2, 3,
etc.). If the value we use to create the path is not animatable, we must
turn it into something that can be animated. For instance, in the following
example, we use a Boolean @State property to determine whether the
graphic of a mouth should be smiling or not, but we turn this value into the
number 0 or 1, so the system can generate values in between to animate
the path (0.1, 0.2, 0.3, etc.).
If the smiling property is true, we send the value 1 to the view, otherwise, we
send the value 0. This means that the system can create an animation by
instantiating this view with values from 0 to 1. The view takes this value
and draws a mouth with the curve defined by an addCurve() method.
The only difference with previous Shape structures is that now we have an
animatableData property that provides the system with the value to animate.
The system accesses this property, gets the value of the smile property from
it, increases or decreases this value by a small amount, sends the result
back to the animatableData property, and the path is redrawn with this new
value.
Because the values received by the Face view are between 0 and 1, we must
convert them to a range we can use to draw the mouth. First, we make
sure that the value is between 0 and 1 with the min() and max() functions.
After that, we calculate the position of the bottom of the mouth according
to this value. If the value is close to 0, the bottom of the mouth will be
close to the top of the view, and if the value is close to 1, it will be close to
the bottom of the view, so we can get a mouth that is smiling or not.
The content of a Canvas view can also be animated. The process requires to
redraw the canvas over and over again. For this purpose, SwiftUI includes
the following view.
The TimelineView view can be used for any task that requires updating the
interface after a period of time. This may involve anything from single
strings to complex graphics. When the interface needs simple updates, we
can use a periodic or explicit schedule, as shown next.
In this example, the TimelineView view begins drawing the views from the
current date and does it again every two seconds. In the closure, we use
the value produced by the view to get the date when the drawing is
performed, extract the seconds, and show the number on the screen.
The TimelineView view works with any content, but it is particularly useful
with the Canvas view. SwiftUI even defines a specific schedule structure to
create animations with a canvas that can be obtained from the animation
property or the animation() method. There is a caveat, though. The system
doesn't know what we want to animate on the canvas, so we must take
care of everything, including the frequency of the animation, the positions
of the graphics, and more. The following example illustrates how to create
a simple animation that repositions a circle on the screen after a few
fractions of a second.
Notice that we have defined a class to store the position of the circle and
the required values to control the frequency of the animation. This is a
simple class that we need to store and update the values. The class doesn't
conform to the ObservableObject protocol in this case because we don't need
to store any state, but the values could have also been stored in an
observable object or the model if necessary.
The timing is controlled from the date produced by the TimelineView view.
From this value, we get the current date, extract the seconds with the
timeIntervalSinceReferenceDate property, check how many seconds has passed
since the last iteration, and update the position of the circle if the
difference is greater than the maximum time set by the maxTime property.
Notice that after the position of the circle is determined, we assign the
current date in seconds to the lastTime property to know when the last
iteration happened, so the graphics are only updated every 0.2 seconds.
The previous example shows how to update the graphics on the canvas,
but it doesn't create any animation. To animate a graphic on the canvas,
we must drawn every single frame. This means calculating the position of
each graphic at every step of the animation. To demonstrate how this
works, we are going to animate a single circle at the center of the screen.
The animation is produced by controlling the circle's radius.
class ContentViewData {
var radius: CGFloat = 0
var step: CGFloat = 5
var lastTime: Double = 0
var maxTime: Double = 0.02
}
struct ContentView: View {
let contentData = ContentViewData()
As we have seen before, we can add or remove views from the interface
depending on a state (see Chapter 6, Listing 6-27). The process by which
the view appears or disappears from the screen is called Transition. By
default, the system doesn't use any transition, it just displays or removes
the view, but we can assign a specific transition to a view with the
following modifier.
SwiftUI defines standard transitions. The following are the type properties
provided by the AnyTransition structure to create them.
The AnyTransition structure defines the type of transition, but for the
transition to be applied, we must define the type of animation used to
produce it. The AnyTransition structure provides the following method for
this purpose.
animation(Animation)—This method applies the animation
defined by the argument to the transition.
The button in this view toggles the value of the showInfo property. When the
value is true, a Text view is added to the interface, otherwise, the view is
removed, but because of the scale transition, the view expands until it
reaches its natural size when the value of the property is true, and contracts
until it disappears from the screen when it is false.
The transitions we have implemented so far are symmetric; they are the
same when the view is added to the interface and when the view is
removed. To create an asymmetric transition, the AnyTransition structure
includes the following type method.
In addition to defining one transition for the insertion and another for the
removal, we can combine two transitions with the following method.
The following example combines a type scale transition with a type opacity
transition. The text fades in and out and also scales while it appears and
disappears from the screen.
This example creates a transition that moves the view in and out of the
interface from the left side of the screen. Notice that this transition
reproduces the slide transition, but in this case the view always uses the
same side of the screen.
Of course, multiple views can transition at the same time, and because the
interface is updated all at once, the animations are coordinated. For
instance, we can use the same showInfo property to show and hide two Text
views.
As always, the showInfo property is initialized with the value false. This means
that the Text view with the string "Left" is shown on the left hand side of
the screen. But when the button is pressed, the value of the showInfo
property is toggled and the Text view with the string "Right" is shown
instead. Both transitions are animated with a scale effect, so they shrink or
expand to appear or disappear from the screen.
The transitions in this example look coordinated, but the strings are
displayed by two different Text views and therefore each animation is
independent. If we want to transition from one view to another with a
single animation, we must declare both views to be the same. SwiftUI
includes the following modifier for this purpose.
This example includes the same views as before. There is a Text view that
appears on the left and another on the right, but because we apply the
matchedGeometryEffect() modifier to both of them with the same identifier and
assign them to the same namespace, they are animated as one view. In
this case, when the value of the showInfo property changes, the views move
from left to right and right to left, and also the text gradually changes from
"Left" to "Right", and vice versa. Notice that for the transition to be
animated, we had to animate the state change with the withAnimation()
function.
The most common gesture used in a mobile device is a tap gesture, which
is detected when the user touches the screen with a finger. Because of
how common it is to work with this gesture, SwiftUI defines two
convenient modifiers to process it.
The code in Listing 12-1 defines an Image view of a size of 160 by 200 points.
The onTapGesture() modifier is applied to the view to detect the tap, and the
sheet() modifier to present a sheet. The following is the ShowImage view
opened by the sheet.
This view creates an Image view and expands it to fill the sheet, including
the safe area. As a result, the interface presents a small image on the
screen, and when the user taps on it, it opens a sheet to show it in full size.
The long press gesture is similar to the tap gesture, but the system waits a
moment before confirming the gesture and performing the task. With the
modifier, we can set the waiting time and also perform a
onLongPressGesture()
task while the user is pressing and waiting for the gesture to complete, as
in the following example.
This is the same example as before but now we apply a long press gesture
to the Image view, so the user must hold the finger in position for a moment
to open the sheet. In this case, we set the waiting time to 1 second and the
maximum distance to 10 points, so the user cannot move the finger more
than 10 points away from the initial position or the gesture is invalidated.
The closure performed while the view is being pressed modifies a @State
property called pressing that we use to set the view's opacity. When the user
puts the finger on the view, the value received by the closure is true, so the
value 0 is assigned to the opacity() modifier, and when the user moves the
finger away, lifts the finger, or the gesture ends, the closure receives the
value false and therefore the value 1 is assigned to the view's opacity. The
process is animated with an easeInOut animation that lasts 1.5 seconds.
Because the animation is longer than the waiting time for the gesture, the
sheet is opened before the image completely disappears, which provides
the necessary feedback for the user to know that it must wait for the
process to finish.
Because views may sometimes overlap and some may have implemented
their own gestures, the system must decide whether a view should process
a gesture or pass it to other views. The process of finding the view the user
wants to interact with and decide whether it should respond to the gesture
or not is called Hit Testing. The View protocol defines the following
modifiers to control this process.
The view in Listing 12-4 adds a Toggle view below the image to control the
value of a @State property. We use this property to determine if hit testing
is allowed on the Image view. The initial value of the property is set to false,
so the user is not able to tap on the image to open the sheet, but when the
switch is turned on, the value true is assigned to the property and therefore
the gesture is recognized by the Image view.

12.2 Gesture Structures

The gestures processed by the onTapGesture() and onLongPressGesture()
modifiers are defined by structures that conform to the Gesture protocol.
The following are the most frequently used.
Due to the simplicity of the tap gesture, there is not much difference
between applying the onTapGesture() modifier or implementing the TapGesture
structure. The structure can also define the number of taps required for
the gesture to be recognized, and since there are no changes to report
over time, it only uses the onEnded() method to perform a task when the
gesture is detected. The following example reproduces the previous
project, but this time we define the gesture recognizer with a TapGesture
structure.
Do It Yourself: For this example, you need the ShowImage view defined
in Listing 12-2. Update the ContentView view with the code in Listing
12-6. You should see a small image on the screen. Tap the image to
open the sheet.
Long Press Gesture

.gesture(LongPressGesture(minimumDuration: 1)
.updating($pressing) { value, state, transaction in
state = value
transaction.animation = Animation.easeInOut(duration: 1.5)
}
.onEnded { value in
expand = true
}
)
.sheet(isPresented: $expand) {
ShowImage()
}
}
}

enum PressingState {
case active
case inactive
.gesture(LongPressGesture(minimumDuration: 1)
.updating($pressingState) { value, state, transaction in
state = value ? .active : .inactive
transaction.animation = Animation.easeInOut(duration: 1.5)
}
.onEnded { value in
expand = true
}
)
.sheet(isPresented: $expand) {
ShowImage()
}
}
}

This example works like before, but now we use an enumeration to keep
track of the state of the gesture. The enumeration is called PressingState and
it includes two cases, active and inactive, and a computed property that
returns a Boolean value according to the current value of the instance (true
for active and false for inactive). Now, instead of defining a @GestureState
property of type Bool to store the value received by the updating() method,
we can define a property of type PressingState and store an enumeration
value. We call this property pressingState and assign it to the updating()
method. When the method is called, we assign the value active or inactive to
this property depending on the value received by the method. When it is
time to read the state in the opacity() modifier, instead of reading the
@GestureState property directly, we get the Boolean value from the isActive
computed property defined by the enumeration. If the current value of the
pressingState property is active, the isActive property returns true and the
opacity is set to 0, otherwise the value returned is false and the opacity is
set to 1.
The result is the same as before, but using enumeration values becomes
necessary when working with more complex gestures or when multiple
gestures are combined.
Magnification Gesture

The magnification gesture is often called the Pinch Gesture because it is the
gesture that is detected when the user spreads two fingers apart or brings
them together as if pinching the screen. This gesture is mostly
implemented to let the user zoom an image in and out.
The value sent to the updating(), onChanged() and onEnded() methods is a CGFloat
that represents a multiple of the scale that we must multiply by the current
scale to get the final scale of the picture, as in the following example.
.gesture(MagnificationGesture()
.updating($magnification) { value, state, transaction in
state = value
}
.onEnded { value in
zoom = zoom * value
}
)
}
}

The code in Listing 12-9 defines two states, one to keep track of the
magnification and another to store the final value. The idea is to allow the
user to zoom in and out multiple times. While the gesture is performed,
the magnification value is stored in the magnification property, but the value
of the zoom property is only modified when the gesture is over, so the next
time the user tries to zoom the picture, the new scale is calculated from
the last one.
To set the scale of the image to the one selected by the user, we apply the
scaleEffect() modifier to the Image view and calculate the new scale by
multiplying the value of the zoom property (the last scale set by the user) by
the value of the magnification property (the multiple generated by the
gesture). As a result, the image is zoomed in and out following the
movement of the fingers.
The example in Listing 12-9 allows the users to zoom the image in and out
as far as they want, but most of the time we must limit the scale of the
view to values that make sense for the interface and the purpose of our
application. To establish these limits, the scale must be controlled in two
places: when it is applied to the view with the scaleEffect() modifier, and
when the gesture ends and the final scale is assigned to the zoom property.
.gesture(MagnificationGesture()
.updating($magnification) { value, state, transaction in
state = value
}
.onEnded { value in
zoom = getCurrentZoom(magnification: value)
}
)
}
func getCurrentZoom(magnification: CGFloat) -> CGFloat {
let minZoom: CGFloat = 1
let maxZoom: CGFloat = 2
The rotation gesture is recognized when the user touches the screen with
two fingers and performs a circular motion. It is often used to rotate an
image. As with previous gestures, if we want to let the user perform the
gesture multiple times, we must keep track of two states, one for the
current rotation and another for the last rotation. The value produced by
the gesture is a structure of type Angle. We worked with this structure
before. It includes two type methods, one to create an instance with a
value in degrees (degrees(Double)) and another to do it with a value in radians
(radians(Double)), but in our example we are going to rotate an image to
follow the fingers, and for that we just need to add the current angle to the
delta angle produced by the gesture.
.gesture(RotationGesture()
.updating($rotationAngle) { value, state, transaction in
state = value
}
.onEnded { value in
rotation = rotation + value
}
)
}
}

This example applies the rotationEffect() modifier to rotate the view. The
angle is calculated by adding the values of the two state properties. We
also add the current rotation to the previous one when the gesture ends to
preserve the current state in case the user wants to rotate the image again
from that angle.
When we tap on the three dots, the system shows a menu with three
options (Figure 12-4, right). The Full Screen option assigns the entire
screen to the app, the Split View option splits the screen in two to show
the current app on the left and another app on the right, and the Slide
Over option moves the app to an overlay window that is displayed on top
of other apps. If we select the second or third options, the screen will be
shared by two apps, so we can drag and drop elements between them.
To allow the user to drag and drop elements from and into our app, we
must tell the system which views can be dragged or accept a drop. SwiftUI
includes the following modifiers for this purpose.
The drag and drop gestures are performed on the views, but the data to be
transferred is determined from code. This doesn't mean that we can send
any value we want, the data must be presented in a way apps can
recognize. For this purpose, the framework defines the Transferable protocol.
This protocol prepares the data to be sent and processes the data received
in a drag and drop operation. Although we can conform to this protocol
from our custom data types to transfer any value we want, some Swift data
types and SwiftUI views do it by default. For instance, if we want to let the
user drag an image from our app to another app, we can use the Image
view.
In order for the user to be able to drop elements into our app, we must
provide a view capable of receiving this data. To turn a view into a possible
destination for a drag and drop operation, we must apply the
modifier. The modifier takes a data type that determines the
dropDestination()
type of data the view can receive and a closure to process it. As before, the
type must conform to the Transferable protocol. For instance, we can use an
Image view.
The closure assigned to the action argument receives two values: an array
with a list of the elements dropped by the user, and a CGPoint structure
with the position within the view where the elements were dropped.
Because in this example we process the data as Image types, the user can
only drop images and the values are automatically turned into Image views,
so we can assign it directly to a @State property and show it on the screen.
Notice that the closure must return a Boolean value to indicate the result
of the operation. If we are able to get the values and process them, we
must return true, otherwise false.
Figure 12-7: Dropping an image into our app
At the time of writing, the Image structure can only process PNG images.
This is why, if we drag any other image than the husky, it won't be
transferred to our app (The rest of the images provided by the simulator
are in JPG format). To provide more alternatives to the user, instead of
converting the image directly into an Image view, we can store it in a Data
structure and convert the data later into a UIImage object.
Converting the data into an image is easy. Once we tell the dropDestination()
modifier to only receive Data values, the data received is stored in a Data
structure that we can turn into a UIImage object and use it create the Image
view, as shown next.
Listing 12-15: Processing the image as data

struct ContentView: View {
@State private var picture: Image = Image("nopicture")
@State private var didEnter: Bool = false
So far, we have use an Image view and a Data structure to transfer the data.
These data types conform to the Transferable protocol and therefore we can
use them to transfer data in a drag and drop operation without any
additional work. But custom data types, including structures and classes,
can be used as well. All we need to do is to make them conform to the
Transferable protocol, which only requirement is the implementation of the
following type property.
FileRepresentation(contentType: UTType,
shouldAttemptToOpenInPlace: Bool, exporting: Closure,
importing: Closure)—This initializer creates a structure that
represents files. The contentType argument determines the types of
files the user is allowed to drag and drop, the
shouldAttemptToOpenInPlace argument indicates if the receiver can
access the original file, the exporting argument provides the file to be
sent in a drag operation, and the importing argument creates the file
from the information received from a drop operation. The structure
also includes two more initializers to only import or export files:
FileRepresentation(importedContentType: UTType, shouldAttemptToOpenInPlace:
and FileRepresentation(exportedContentType: UTType,
Bool, importing: Closure)
shouldAllowToOpenInPlace: Bool, exporting: Closure).
The values we need are the Description, the Identifier, and the Conforms
To. The Description is just a text to describe the type, the Identifier must be
unique and therefore it is recommendable to declare it with an inverted
domain, as we did in our example, and the Conforms to option is a
predefined UTType that closely matches what our type is going to represent.
In this case, we use the public.data type, so the system knows we are
transferring raw data.
Once we have the custom content type, we need to extend the UTType
structure to include a type property that represents it. In the following
example, we create a custom structure to manage the data (an image and
an identifier), and extend the UTType structure with a property called product
to store our custom type.
init() {
listPictures = [
PictureRepresentation(image: UIImage(named: "spot1")!.pngData()!),
PictureRepresentation(image: UIImage(named: "spot2")!.pngData()!),
PictureRepresentation(image: UIImage(named: "spot3")!.pngData()!)
]
}
}

CLLocationCoordinate2D(latitude: CLLocationDegrees,
longitude: CLLocationDegrees)—This initializer creates an
CLLocationCoordinate2D structure with the values determined by the
arguments. The latitude argument is a typealias of Double that
determines the location's latitude, and the longitude argument is a
typealias of Double that determines the location's longitude.
The first step is to define all these values to tell the Map view what region
of the map to show. Because these values may change and more may be
required later, it is better to manage the map from an observable object or
the model, as we do in the following example.
The Map view shows the map in the region determined by the
MKCoordinateRegion structure assigned to the region property. In this case,
that's the area around the Apple store in New York City.
The latitude and longitude values in the region property are updated every
time the user drags the map to a different area, as shown in the following
example.
When the user scrolls the map to a different area, the center of the region
changes, and because we initialized the Map view with a bidirectional
binding, those values are assigned back to the region property, and
therefore we can read them, process them, or show them to the user, as in
this example.
By default, the Map view allows the user to zoom and pan the map, but the
view's initializer can include the interactionModes argument to enable
only zooming or panning. For instance, we can disable panning to not allow
the user to change the region.
The previous examples set the visible area around the Apple Store.
Because this is a relevant location, the map shows an icon with the name
of the store, but this is not always the case. Most locations do not show
any reference at all, and the user has to guess where the exact location
actually is. To add graphics to the map and mark locations, we use
annotations.
Annotations provide additional information of a particular location. SwiftUI
defines two views to create them: the MapMarker view to create
annotations with standard views, and the MapAnnotation view to create
annotations with custom views.
Again, the first step is to provide the data required to configure the map
and show the annotations. For the annotations, we need a structure that
conforms to the Identifiable protocol and includes at least a
property with the annotation's location. In the
CLLocationCoordinate2D
following example, we call this structure PlaceAnnotation.
init() {
let coordinates = CLLocationCoordinate2D(latitude: 40.7637825011971, longitude:
-73.9731328627541)
region = MKCoordinateRegion(center: coordinates, latitudinalMeters: 1000, longitudinalMeters:
1000)
The Map view in this example reads the array of PlaceAnnotation structures in
the annotations property and calls the closure assigned to the
annotationContent argument to create a MapMarker view to represent each
annotation.
For testing, we included a single annotation in the model of Listing 13-5
with the location of the Apple store, so the map shows a ballon to mark
that place.
Figure 13-3: Annotation on the map
The MapMarker view creates a standard view with a predefined design, but
we can use any views we want to represent the annotations. All we need to
do is to provide the views with a MapAnnotation view instead.
Of course, we can design the annotation with any view we want, including
images and text. The following example embeds an Image and a Text view in
a VStack to show an icon at the top and the annotation's name below.
The Map view doesn't provide any tools to allow the user to select an
annotation, but we can apply the onTapGesture() modifier to the views to
allow the user to select them. The trick is to use the selected property we
have included in the PlaceAnnotation structure. When the value of this
property is true, which means that the annotation was selected, we can
represent the selection on the interface by modifying the views. For
instance, we can expand the image of the selected annotation, as we do in
the following example.
The MapAnnotation view in this example includes a VStack with the same Image
and Text views used before, but now the width and height for the frame()
modifier applied to the Image view depend on the value of the annotation's
selected property. If the value is true, the image will be 60x60 pixels,
otherwise, the size assigned to the image is 40x40 pixels, as before.
When the user taps on an annotation to select it, we must assign the value
true to its selected property, and also assign the value false to the rest of the
annotations to deselect them. For this purpose, we define a for in loop. The
loop implements the enumerated() method to get the item but also its index.
If the item's identifier is equal to the identifier of the annotation selected
by the user, we toggle the value (if the annotation is deselected, we select
it, otherwise we deselect it), but if the item represent any of the
annotations that were not tapped by the user, we assign the value false to
make sure they are all deselected.
Figure 13-6: Selected and deselected annotation
MKLocalSearch(request: MKLocalSearchRequest)—This
initializer creates an MKLocalSearch object to perform a search request.
start()—This asynchronous method performs a search and returns an
MKLocalSearchResponse object with the results.
The Local Search service was designed to find all the places that match the
query. The framework defines the MKMapItem class to represent a place.
The following are some of the properties included by this class to return
the data from the place.
There are different ways an app can perform a search and display the
results. As an example, we are going to search for places associated with
the word "Pizza" as soon as the view is loaded.
await MainActor.run {
appData.annotations = []
for item in items {
if let location = item.placemark.location?.coordinate {
let place = PlaceAnnotation(name: item.name ?? "Undefined", location: location)
appData.annotations.append(place)
}
}
}
}
}
}

The code in Listing 13-10 defines the setAnnotations() method to search for
places associated with the term "Pizza" and adds an annotation to the
model for each location found. When the view is loaded, the task() modifier
asynchronously calls this method to start the process. The method creates
a request with the query "Pizza" and the region defined in the model to
find places of interest around the current location. The request is then
used to create the MKLocalSearch object and the search is initiated by calling
the start() method. This method is asynchronous, so we must wait for the
results. Once the results are back from Apple servers, we remove the
current annotations and create a loop to read all the places found and
store them in the model.
Devices can detect the user's location, and the Map view's initializer
includes the showsUserLocation argument to show it on the map, but first
we must ask the user for permission. There are two types of authorization.
We can ask permission to get updates only while the app is active (the app
is being used by the user at the time), or all the time (even when the app
moves to the background). The Core Location framework defines the
CLLocationManager class to manage locations and get authorization from the
user. The following are some of the properties and methods included in
this class for this purpose.
Every time the CLLocationManager object needs to report the user's location,
it calls methods on a delegate object. The framework defines the
CLLocationManagerDelegate protocol to create this delegate. The following are
some of the methods included in the protocol.
locationManagerDidChangeAuthorization(CLLocationManage
r)—This method is called on the delegate when the authorization
status changes (the user grants or denies access to his or her
location).
locationManager(CLLocationManager, didFailWithError:
Error)—This method is called on the delegate when the manager fails
to obtain the user's location.
locationManager(CLLocationManager, didUpdateLocations:
[CLLocation])—This method is called on the delegate when new
locations are found.
The first thing we need to do is to define the delegate object for the
location manager. As always, it could be any object we want, but it is
recommended to use the model. There is only one caveat. The
CLLocationManagerDelegate protocol inherits from NSObjectProtocol, a protocol
defined in Objective-C, so our delegate object needs to inherit from the
NSObject class. This also requires us to override the NSObject's initializer and
call the initializer on the superclass, as shown below.
override init() {
super.init()
manager.delegate = self
manager.desiredAccuracy = 100
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations:
[CLLocation]) {
if let coordinates = locations.first?.coordinate {
region = MKCoordinateRegion(center: coordinates, latitudinalMeters: 1000,
longitudinalMeters: 1000)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Error")
}
}

In addition to the requirements of the NSObject class, there are a few more
steps we must follow in our model for the system to work properly. For
starters, we need to get the model to conform to the
CLLocationManagerDelegate protocol and implement at least two methods. We
need the locationManager(CLLocationManager, didUpdateLocations:) method to read
the locations detected by the system, and the
locationManager(CLLocationManager, didFailWithError:) method to respond to
errors. (If this method is not implemented, the app crashes when the
location cannot be determined or an error is found.) We also need to store
the CLLocationManager object in a property to make sure it remains in
memory for as long as we need it, and configure the manager when the
model is initialized. For our example, we assign the model as the delegate
and then set the desired accuracy to 100 meters. This makes sure that the
system returns a location in a timely manner.
Notice that the region property is initialized with an empty
MKCoordinateRegion() object, so we can show the map while we wait for a
location. When the delegate method is called to report a new location, we
get the CLLocationCoordinate2D structure with the user's location from the
first value in the array (the method can receive multiple consecutive
locations), use it to generate the new MKCoordinateRegion structure, and
assign it to the region property to update the map.
There are also a few requirements for the view. We must initialize the Map
view with the showsUserLocation argument and the value true to get the
map to show an annotation on the user's location, ask the user for
authorization with the requestWhenInUseAuthorization() method as soon as the
view is shown on the screen, and request a location with the requestLocation()
method when a button is pressed.
The view now includes a button on top of the map to start the process.
When the button is pressed, the requestLocation() method is called, the
manager gets the user's location, calls the protocol method to report it to
the delegate object, the method updates the value of the region property
with the new coordinates, and the Map view zooms in to that region and
displays a blue circle on the place.
Do It Yourself: Update the model with the code in Listing 13-11 and
the ContentView view with the code in Listing 13-12. Add the "Privacy -
Location When In Use Usage Description" option to the app's
configuration, as explain in Chapter 5 (see Figure 5-34). Run the
application on the iPhone simulator. If you want to see your current
location, you must run the app on a real device. Authorize the app to
access your location. Press the My Location button. You should see a
blue circle over your location on the map.
Notifications are created from the Notification class. The post() method
automatically creates a Notification object to represent the notification we
want to post, but we can also do it ourselves from the Notification class
initializer.
The class includes the following properties to read the values of the
notification.
The model must store the values inserted by the user, as always, but also
read the notifications to update the main view.
init() {
Task(priority: .background) {
await readNotifications()
}
}
func readNotifications() async {
let center = NotificationCenter.default
let name = Notification.Name("Update Data")
This model defines a @Published property of type Int to update the view with
the total number of titles inserted by the user, and a normal property
called titles to store the values.
The Notification Center creates an asynchronous sequence with all the
notifications received (see Asynchronous Sequences in Chapter 9). The
sequence is managed by a Notifications object that we can get from the
notifications() method. To read this sequence, we create an asynchronous for
in loop with this object.
Because we must wait for the notifications to arrive, we mark the for in loop
with the await keyword and put it inside an asynchronous method. Every
time a notification is received, the loop performs a cycle, we get the total
number of titles stored in the titles array, and assign it to the label. Notice
that we call the run() method on the Main Actor to make sure that the
statement that interacts with the label runs in the main thread.
The initial view shows the value of the total property on the screen and
includes a button in the navigation bar to open a second view that allows
the user to insert new values.
The values in the model are added from the AddBook view opened by the
button in the navigation bar. The view includes a TextField view for the user
to insert a new value and a button to save it. When the button is pressed,
we store the value in the model and send a notification to let the rest of
the application know that a new value is available.
When a name is inserted into the TextField view and the Save button is
pressed, we call a method to store the value and post the notification. To
post the notification, we get a reference to the NotificationCenter object
assigned to the app, define a custom name for the notification ("Update
Data"), and post it with the post() method.
In the model, the asynchronous for in loop defined in the readNotifications()
method detects that there is a new notification available, performs a new
cycle, the number of titles stored in the titles property is assigned to the total
property, and the initial view is updated with the new value.
The code in Listing 14-4 declares a dictionary with the "type" key and the
value inserted by the user and assigns it to the userInfo argument of the
post() method. Now, we can check this value from our model every time a
notification is received.
The values from the dictionary are returned as values of type Any, so we
must cast them to the right type. In the for in loop of Listing 14-5, we read
the value of the "type" key, cast it as a String, and then compare it with the
string "Miracle". If the values match, we print a message on the console.
If we do not want to post any more notifications, we can stop the process
from the AddBook view, but if we want to stop processing the notifications
from a receiver, we must cancel the task by calling the cancel() method, as
we did in Chapter 9 (see Listing 9-4).
In the following example, we create a timer in the initializer of the
ApplicationData class to cancel the task after 10 seconds, so the initial view is
no longer updated with any of the values inserted by the user after the
time expires.
Listing 14-6: Cancelling the task

init() {
let myTask = Task(priority: .background) {
await readNotifications()
}
Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { timer in
myTask.cancel()
}
}

Besides the notifications posted by our app, the system also posts
notifications to the Notification Center all the time to report changes in the
interface or the device. There are dozens of notifications available. They
work exactly like the custom notifications studied before but are
predefined as type properties of UIKit classes. We will probably never use
most of the notifications available, but some are very useful. For example,
the UIKit framework defines a class called UIWindow, used to create the
app's windows, that posts notifications to report the state of the keyboard.
The following are the most frequently used.
Listing 14-8: Scrolling the interface to make room for the keyboard

struct ContentView: View {
@FocusState var focusTitle: Bool
@State private var inputTitle: String = ""
The only difference from the previous example is that we have now
embedded the views into a ScrollView view, so instead of adapting the
interface, the system scrolls the views to keep the TextField view visible
when the keyboard is opened.

This is the automatic behavior provided by SwiftUI, but we can still
introduce modifications by listening to the keyboard notifications. For
instance, when the interface is embedded in a ScrollView view, the TextField
view scrolls enough to become visible, but there is no margin between this
view and the keyboard (see Figure 14-3, right). If we want to improve the
design, we can apply some modifiers to the views and change the styles
when the state of the keyboard changes.
Although we can perform these changes from the views, it is better to do it
from the model. For example, the following model listens to keyboard
notifications and modifies the value of a @Published property to change the
offset of the ScrollView view by -20 points when the keyboard is opened.
init() {
Task(priority: .background) {
await receiveNotificationOpen()
}
Task(priority: .background) {
await receiveNotificationClose()
}
}
func receiveNotificationOpen() async {
let name = await UIWindow.keyboardDidShowNotification
for await _ in center.notifications(named: name, object: nil) {
await MainActor.run {
scrollOffset = -20
}
}
}
func receiveNotificationClose() async {
let name = await UIWindow.keyboardDidHideNotification
for await _ in center.notifications(named: name, object: nil) {
await MainActor.run {
scrollOffset = 0
}
}
}
}

The asynchronous for in loop waits for new values to come in. This means
that we cannot declared one loop after another because the second loop
will never be executed. This is the reason why in this example we have
created two tasks, one to listen to the keyboardDidShowNotification notification
and another to listen to the keyboardDidHideNotification notification. When a
keyboardDidShowNotification notification is received, we assign the value -20 to
the scrollOffset property, and when a keyboardDidHideNotification notification is
received, we assign the value 0.
In the view, we can use the value of the scrollOffset property to set the
ScrollView view's offset.
Listing 14-10: Modifying the interface when the keyboard state changes

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@FocusState var focusTitle: Bool
@State private var inputTitle: String = ""
The ScrollView view now moves up 20 points when the keyboard is opened
and goes back to its original position when it is closed, generating a
padding between the views and the keyboard.
For accurate detection, the device needs to activate the accelerometer. For
this purpose, the UIDevice class defines the following methods.
beginGeneratingDeviceOrientationNotifications()—This
method enables the accelerometer and begins delivering notifications
to communicate changes in the orientation.
endGeneratingDeviceOrientationNotifications()—This method
tells the system that the accelerometer is no longer required and
stops the delivery of notifications.
And the following are the properties defined by the UIDevice class to access
the device and return the current orientation.
As always, we can listen to the notification from the model and perform
the necessary changes. In this cases, we have decided to include a
@Published property to update the views when the device's orientation goes
from portrait to landscape and vice versa.
Listing 14-11: Detecting changes in the orientation

import SwiftUI
init() {
Task(priority: .background) {
await receiveNotification()
}
}
func receiveNotification() async {
let center = NotificationCenter.default
let name = await UIDevice.orientationDidChangeNotification
for await _ in center.notifications(named: name, object: nil) {
await MainActor.run {
let device = UIDevice.current
let orientation = device.orientation
isLandscape = orientation.isLandscape
}
}
}
}

requestAuthorization(options: UNAuthorizationOptions)—
This asynchronous method requests authorization from the user to
show notifications and returns a Boolean value to report the result.
The options argument is a set of properties that determine the type
of notifications we want to show. The properties available are badge,
sound, alert, carPlay, criticalAlert, provisional, and announcement.
UNNotificationSound(named: UNNotificationSoundName)—
This initializer creates a UNNotificationSound object with the sound
specified by the named argument.
default—This type property returns a UNNotificationSound object with
the sound defined by the system.
User Notifications are posted to the User Notification Center and then
presented by the system when a certain condition is met. These conditions
are established by objects called Triggers. There are three types of triggers
available for Local Notifications: Time Interval, (the notification is delivered
after a certain period of time), Calendar (the notification is delivered on a
specific date), and Location (the notification is delivered in a specific
location). The framework defines three classes to create these triggers:
UNTimeIntervalNotificationTrigger, UNCalendarNotificationTrigger, and
UNLocationNotificationTrigger.
UNTimeIntervalNotificationTrigger(timeInterval:
TimeInterval, repeats: Bool)—This initializer creates a Time
Interval trigger that will deliver the notification after the period of
time determined by the timeInterval argument (in seconds). The
repeats argument determines if the notification will be delivered once
or infinite times.
UNCalendarNotificationTrigger(dateMatching:
DateComponents, repeats: Bool)—This initializer creates a
Calendar trigger that delivers the notification at the date determined
by the dateMatching argument. The repeats argument determines if
the notification will be delivered once or infinite times.
UNLocationNotificationTrigger(region: CLCircularRegion,
repeats: Bool)—This initializer creates a Location trigger that
delivers the notification when the device is inside a region in the real
world determined by the region argument. The repeats argument
determines if the notification will be delivered once or infinite times.
This view provides a TextField view for the user to insert a message to send
with the notification and a button to post it. But before even enabling the
button, we must ask for permission with the requestAuthorization() method.
The task is initiated by the task() modifier when the view is loaded. In the
closure assigned to this modifier, we get a reference to the
UNUserNotificationCenter object assigned to our app, ask the user to authorize
the app to post notifications of type alert and sound, and then enable or
disable the button on the interface according to the value returned.
(Notice that before the value is assigned to the isButtonDisabled property, it is
inverted with the ! symbol.)
When the requestAuthorization() method is called, it creates an Alert View with
a message and two buttons to let the user decide what to do, as shown
below.
When the use presses the button to post a notification, we initiate another
asynchronous task, this time to check whether the app is still allowed to
post notifications. For this purpose, the UNUserNotificationCenter class includes
the notificationSettings() method. We should always consult this method
before sending notifications to make sure that the app is still authorized to
do it. If the value of the authorizationStatus property is equal to authorized, it
means that we can proceed. The add() method used to send a notification is
also asynchronous, so after the authorization is confirmed, we call our own
asynchronous method to send the notification.
do {
let center = UNUserNotificationCenter.current()
try await center.add(request)
await MainActor.run {
inputMessage = ""
}
} catch {
print("Error: \(error)")
}
}

do {
let center = UNUserNotificationCenter.current()
try await center.add(request)
await MainActor.run {
inputMessage = ""
}
} catch {
print("Error: \(error)")
}
}

let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
let center = UNUserNotificationCenter.current()
try await center.add(request)
await MainActor.run {
inputMessage = ""
}
} catch {
print("Error: \(error)")
}
}
func getThumbnail(id: String) async -> URL? {
let manager = FileManager.default
if let docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = docURL.appendingPathComponent("\(id).png")
if let image = UIImage(named: "husky") {
if let thumbnail = await image.byPreparingThumbnail(ofSize: CGSize(width: 100, height:
100)) {
if let imageData = thumbnail.pngData() {
if let _ = try? imageData.write(to: fileURL) {
return fileURL
}
}
}
}
}
return nil
}

Because we need to load, process, and save the image in a file, we moved
the code to a new method called getThumbnail(). In this method, we get the
URL of the Documents directory, append the name of the file, then load
the image from the Asset Catalog, convert it to data, reduce the size with
the byPreparingThumbnail() method, and store it (see Chapter 10, Listing 10-
21). The file's URL is returned by the method and used by the code to
attach the image to the notification. The result is shown below. The image
is displayed inside the banner and expanded when the user taps and holds
the finger over the notification (or drags the notification down, depending
on the state of the application).
Asking the user for permission can be a bit disruptive for some
applications. If we consider that due to the characteristics of our app the
user's acceptance to receive notifications may be implicit, we can post
provisional notifications. These are called quiet notifications and only show
up in the Notification Center (they are not displayed in the Locked or Home
screens). They include buttons for the user to decide whether to keep
them or turn them off.
To get our app to post provisional notifications, all we need to do is to add
the provisional option to the requestAuthorization() method, as shown next.
If the user is working with the app when a notification is delivered, the
notification is not displayed on the screen, but we can assign a delegate to
the User Notification Center to change this behavior. For this purpose, the
framework defines the UNUserNotificationCenterDelegate protocol, which allows
us to do two things: we can decide whether to show the notification when
the app is running, and also respond to actions performed by the user. For
this purpose, the protocol defines the following methods.
userNotificationCenter(UNUserNotificationCenter,
willPresent: UNNotification)—This asynchronous method is called
by the User Notification Center on the delegate when the application
is active and a notification has to be delivered. The method must
return a structure of type UNNotificationPresentationOptions to indicate how
we want to alert the user. The structure includes the type properties
badge, banner, list, and sound.
userNotificationCenter(UNUserNotificationCenter,
didReceive: UNNotificationResponse)—This method is called by
the User Notification Center when the user interacts with the
notification (performs an action). The didReceive argument is an
object with information about the notification and the action
performed.
When a notification is triggered and the app is being executed, the User
Notification Center calls the userNotificationCenter(UNUserNotificationCenter,
willPresent:) method on its delegate to ask the application what to do. In this
method, we can perform any task we want and then return a
UNNotificationPresentationOptions value to specify the type of notification we
want to show.
As always, we can declare any object as the delegate, but it is
recommended to use the model. In the following example, we make the
model conform the UNUserNotificationCenterDelegate protocol, assign it as the
delegate of the UNUserNotificationCenter object, and implement the
userNotificationCenter(UNUserNotificationCenter, willPresent:) method to show the
notification in a banner while the app is running.
All the notifications with the same identifier will be grouped together. The
following example posts multiple notifications and separates them in two
groups called Group One and Group Two.
let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
let center = UNUserNotificationCenter.current()
try await center.add(request)
} catch {
print("Error: \(error)")
}
}
}
await MainActor.run {
inputMessage = ""
}
}

For didactic purposes, we defined one array with the name of the groups
("Group One" and "Group Two") and a for in loop to post three notifications
per group (1...3). To be able to identify the notifications and the groups, we
include these values in the title and body of the UNMutableNotificationContent
object, and to tell the system to which group each notification belongs, we
assign the name of the group to the threadIdentifier property. In total, we
post six notifications in two groups, as shown below.
Notifications can show custom actions in the form of buttons and input
fields that the user can interact with to provide feedback without having to
open our app. The actions are defined by two classes: UNNotificationAction
and UNTextInputNotificationAction.
After the actions are defined, we must create a category to group them
together. The UNNotificationCategory class includes the following initializer to
add actions to a notification.
UNNotificationCategory(identifier: String, actions:
[UNNotificationAction], intentIdentifiers: [String], options:
UNNotificationCategoryOptions)—This initializer creates a
category with the actions specified by the actions argument. The
identifier argument is a string that identifies the category, the
intentIdentifiers argument is an array of strings used to guide Siri to
produce a better response, and the options argument is an array of
properties that determine how the notifications associated to the
category are going to be handled. The properties available are
customDismissAction (processes the dismiss action) and allowInCarPlay
(allows car play to show notifications).
Actions are displayed when the user taps and holds the finger over the
notification (or drags it down, depending on the current state of the
application). In this case, the notification shows a Delete button. If the user
presses this button, the User Notification Center calls the delegate method
to give our application the chance to perform a task. In our example, we
read the actionIdentifier property, compare it with the string "deleteButton"
to confirm that the user pressed the Delete button, and then print a
message on the console in case of success.

Do It Yourself: Update the sendNotification() method with the code in
Listing 14-22 and the ApplicationData.swift file with the code in
Listing 14-23. Remember to inject the ApplicationData object into the
environment (Chapter 7, Listing 7-4). Run the application, post a
notification, and go to the Home screen. Tap and hold the finger on
the notification to reveal the Delete button (Figure 14-13). Press the
button. You should see a message printed on the console.
The text inserted by the user in the text field is sent to the delegate
method. The framework offers a special class to represent the response
called UNTextInputNotificationResponse. To access the value inserted by the user,
we must cast the response object to this class and then read its userText
property, as we did in Listing 14-25. The result is shown below.
@main
struct TestApp: App {
@Environment(\.scenePhase) var scenePhase
Controlling the app state with the values returned by the scenePhase
property is more than enough for most situations, but some old
frameworks still depend on delegate objects assigned to the application. To
control the states of the application and report changes, the UIKit
framework defines the UIApplication class.
When the user taps on the icon to run the app, the system creates an
object of type UIApplication. This object starts a loop to keep the application
running, checks for events, and report changes in the state of the app. The
class includes the following type property to access the object.
From this object, we can check the state of the app. The class includes the
following property for this purpose.
We can read this property to get the current state of the app, as we did
with the scenePhase property before, but some frameworks need more
information. For this purpose, the UIApplication object can also report
changes by calling methods in a delegate object. The UIKit framework
includes the UIApplicationDelegate protocol to define this delegate. The
following are some of the methods included in the protocol.
application(UIApplication, didFinishLaunchingWithOptions:
Dictionary)—This is the first method called by the UIApplication object.
It is called to let us know that all the necessary objects have been
instantiated, and the app is ready to work.
application(UIApplication, configurationForConnecting:
UISceneSession, options: UIScene.ConnectionOptions)—This
method is called when a new Scene (window) is requested by the
system or the user. The method must return a UISceneConfiguration
object with the Scene’s configuration.
application(UIApplication, didDiscardSceneSessions: Set)—
This method is called when the user discards a Scene (closes a
window). The didDiscardSceneSessions argument is a set with
references to the UISceneSession objects representing the Scenes'
sessions.
UIApplicationDelegateAdaptor(DelegateType.Type)—This
initializer creates an instance of the UIApplicationDelegateAdaptor
structure associated with the class specified by the argument.
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(CustomAppDelegate.self) var appDelegate
To report the state of a Scene, the UIApplication object calls methods on the
Scene's delegate, which is defined by the UIWindowSceneDelegate protocol.
The following are the methods available.
The Scenes are configured from the app delegate. For this purpose, the
UIApplicationDelegate protocol includes the application(UIApplication,
configurationForConnecting:, options:) method. To define the configuration, the
UIKit framework includes the UISceneConfiguration class.
This initializer provides the name and the role of the Scene, but we must
also declare the name of the class that is going to be used to create the
Scene's delegate. For this purpose, the UISceneConfiguration class includes the
following property.
The first step to define a Scene delegate is to create a custom class that
conforms to the UIWindowSceneDelegate protocol and implements the protocol
methods.
private init() {
maintext = "Welcome"
}
}

This model includes a @Published property to define the content of a Text
view in the interface. The instance of the model is created by a static
property called shared. Every time we need to access the model, we can
read this property and always get the same instance (see Singletons in
Chapter 3).
As always, we need to inject the model into the environment, but instead
of creating a new instance of the ApplicationData class, as we have done so
far, we must get the instance from the shared property.
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(CustomAppDelegate.self) var appDelegate
@StateObject var appData = ApplicationData.shared
After iCloud is added to the app, it is shown on the panel, below the
signing section. From here, we can select the services we want to activate
and Xcode takes care of creating the entitlements. Figure 15-2, below,
shows the panel with the Key-Value storage service activated.
The best way to test iCloud is by running the app in two different devices,
but Apple has made iCloud services available in the simulator as well.
Thanks to this feature, we can synchronize data between a device and the
simulator to test our app.
For the devices and the simulators to be able to access iCloud services, we
must register our iCloud account in the Settings app. We have to go to the
Home screen, access the Settings app, tap on the option Sign in to your
iPhone/iPad (Figure 15-3, center), and sign in to Apple services with our
Apple ID. We must repeat the process for every device or simulator we
want to use.

15.2 Key-Value Storage

The Key-Value storage system is the User Defaults system for iCloud. It
works the same way, but all the data is stored on iCloud servers instead of
the device. We can use it to store the app’s preferences, states, or any
other value that we need to automatically set on each device owned by the
user. Foundation defines the NSUbiquitousKeyValueStore class to provide access
to this system. The class includes the following methods to store and
retrieve values.
The application must initialize the interface with the current value stored in
iCloud, but it also has to send the value back when a new one is set by the
user, and update it when it is changed from another device. The best way
to manage this information is from the model. We need a @Published
property to store the local value and a few methods to send and receive
the value to and from iCloud.
init() {
storage = NSUbiquitousKeyValueStore()
control = storage.double(forKey: "control")
}
func valueChanged(value: Double) {
if control != storage.double(forKey: "control") {
storage.set(value, forKey: "control")
storage.synchronize()
}
}
func valueReceived() async {
let center = NotificationCenter.default
let name = NSUbiquitousKeyValueStore.didChangeExternallyNotification
for await notification in center.notifications(named: name, object: storage) {
if notification.name == name {
await MainActor.run {
control = storage.double(forKey: "control")
}
}
}
}
}

This model defines a @Published property called control that we use to keep
track of the value set by the stepper, and a normal property to store the
NSUbiquitousKeyValueStore object necessary to access the values in iCloud.
When the observable object is initialized, we create the
NSUbiquitousKeyValueStore object and load from iCloud a value stored with the
"control" key. (If the value doesn't exist, the value returned is 0.)
Next, we define two methods. The valueChanged() method is executed every
time the value of the control property is modified. This method sends the
new value to iCloud with the set() method and then calls the synchronize()
method to make sure the system sends it right away. Notice that because
we want to send the value only when it is modified by the user, we only do
it when the new value is different than the current value. The second
method is called valueReceived(). This method initializes an asynchronous for in
loop to listen to the didChangeExternallyNotification notification. When the
value is modified from another device, the notification is triggered, so we
can read the new value from iCloud and update the control property.
All the job is done by the model, so the view only needs to include a Stepper
and a Text view to recreate the interface illustrated in Figure 15-4, and
implement the onChange() and task() modifiers to call the methods in the
model when necessary.
Listing 15-2: Defining the interface to store and read values from iCloud

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
The same way we can store one value, we can store several. The Key/Value
storage service can manage multiple values using different keys, but if we
want to simplify our work, we can use a structure to store all the values,
encode that structure into data with JSON, and then store only one value in
iCloud containing that data. Our next example follows this approach. We
define a structure to store the user's name, address and city, and then
encode and decode an instance of that structure to store the values in
iCloud.
init() {
storage = NSUbiquitousKeyValueStore()
The process is the same as before. We read the value from iCloud when
the observable object is initialized, and then implement two methods, one
to send the PersonalInfo structure to iCloud when the user inserts new
values, and another to listen to the didChangeExternallyNotification notification
and update the structure when new values are inserted from a different
device. The only difference is in how the value is processed. This time we
are dealing with a structure encoded as a JSON value, therefore we need to
use a JSONDecoder object when a value is received, and a JSONEncoder object
to send the value to iCloud (see Chapter 10, Listing 10-42).
We need two views for this example: one to show the current values and
another to let the user insert new ones. The initial view is simple. All it
needs to do is to get the values from the @Published property, show them on
the screen, and initialize a task to update them every time they are
modified from a different device.
This view includes a button in the navigation bar to open a view called
InsertInfoView in a sheet. The following is our implementation of this view.
This is a normal view with three TextField views to allow the user to insert
and modify the values. Because there might be values already stored in
iCloud, we assign the values in the userInfo property to @State properties to
show them to the user when the view appears. If the user presses the
button to save the values, we create a new PersonalInfo structure and call the
setInfo() method in the model to encode it and send it to iCloud.
The application is ready. When the user inserts new values, they are sent
to iCloud, and when the app is launched or a notification is received, the
values in iCloud are downloaded and shown on the screen.

Do It Yourself: Update the ApplicationData.swift file with the code in
Listing 15-3. Update the ContentView view with the code in Listing 15-
4. Create a SwiftUI View file called InsertInfoView.swift for the code
in Listing 15-5. Run the application on two devices. Press the Change
button. Insert new values and press Save. Wait a few seconds for the
values to appear on the second device.
15.3 iCloud Documents

The Key-Value storage system was developed to store small values. The
purpose is to allow users to set preferences or configuration parameters
across devices. Although very useful, it presents some limitations,
especially on the amount of data we can store (currently no more than 1
Megabyte). An alternative is to activate the iCloud Documents option in
the Capabilities panel and upload files instead.
The operating system uses a container to store iCloud files. This container
is a folder in the app's storage space where iCloud files are created. Once a
file is added, modified or removed from this container, the system
automatically reports the changes to iCloud, so the copies of the app
running in other devices can modify their own container to stay
synchronized. If the container is not created by Xcode, we have to do it
ourselves by pressing the + button (Figure 15-6, number 1). This opens a
window to insert the container's name.
The name must be unique, and the best way to guaranty it is to use the
app's bundle identifier. In our example, we use the bundle identifier to
create the container for an app called TestiCloud. Once the container is
created, we should press the Refresh button to make sure the information
is uploaded to Apple servers right away (Figure 15-6, number 2). After this,
the container is added and selected as the active container for the
application.
Although the files are stored in a container in the device and synchronized
by the system automatically, working with iCloud introduces some
challenges that the FileManager class cannot overcome. The most important
is coordination. Because of the unreliability of network connections, at any
moment iCloud may find different versions of the same file. Modifications
that were introduced to the file from one device may not have reached
iCloud and therefore may later conflict with updates introduced from
another device. The application has to decide which version of the file to
preserve or what data is more valuable when the file is edited from two
different devices at the same time. These issues are not easy to solve and
can turn development into a nightmare. Considering all the problems a
developer has to face, Apple introduced a class called UIDocument designed
specifically to manage files for iCloud. The class includes capabilities to
coordinate and synchronize files of any size, and features that simplify the
manipulation of documents in mobile devices, like progression reports,
automatic thumbnail generation, undo manager, and others.
The UIDocument class was not designed to be implemented directly in our
code; it is like an interface between the app’s data and the files we use to
store it. To take advantage of this class, we must create a subclass and
overwrite some of its methods. Once we define the subclass, we can create
the object with the following initializer.
Once an object is created from our UIDocument subclass, we can manage the
file from the asynchronous methods provided by the class. The following
are the most frequently used.
open()—This asynchronous method asks the UIDocument object to
open the file and load its content. The method returns a Boolean
value to indicate if the operation was successful.
save(to: URL, for: SaveOperation)—This asynchronous method
asks the UIDocument object to save the content of the document on file.
The for argument is an enumeration value that indicates the type of
operation to perform. The values available are forCreating (to save the
file for the first time) and forOverwriting (to overwrite the file’s current
version). The method returns a Boolean value to indicate if the
operation was successful.
close()—This asynchronous method saves any pending changes and
closes the document. The method returns a Boolean value to indicate
if the operation was successful.
Accessing the files is also complicated in iCloud. We cannot just get a list of
files with methods like contentsOfDirectory() from the FileManager class because
there could be some files that have not been downloaded yet to the
device. What we can do instead is to get the information pertaining to the
files. This data is called Metadata, and refers to all the information
associated with a particular file, such as its name, the date it was created,
etc. To get the files' metadata, Foundation defines the NSMetadataQuery class.
This class provides the properties and methods necessary to retrieve the
information and watch for updates.
The results of a query are returned by the results property in the form of an
array of NSMetadataItem objects. This is a simple class defined to contain the
attributes of a file. The class provides the following method to retrieve the
values.
The codes listed below make up the model we need for this application.
The first part, next, initializes the NSMetadataQuery object and starts listening
to the NSMetadataQueryDidFinishGathering notification to update the document
when new information is received.
init() {
metaData = NSMetadataQuery()
metaData.predicate = NSPredicate(format: "%K == %@", NSMetadataItemFSNameKey,
"myfile.dat")
metaData.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
Task(priority: .high) {
let center = NotificationCenter.default
let name = NSNotification.Name.NSMetadataQueryDidFinishGathering
for await notification in center.notifications(named: name, object: metaData) {
if notification.name == name {
await createFile()
}
}
}
metaData.start()
}
}

Because in this application we are only working with one document, the
createFile() method has to perform two tasks. If a file is found, we must
create an instance of MyDocument and assign it to the document property to
make it available for the views, but if no file is available, we must create
one. To know if the query has found a document, we check the value of the
resultCount property of the NSMetadataQuery object. This property returns the
number of documents (files) available. If the number is greater than 0, we
get the metadata of the first document on the list with the result(at:)
method, get its URL with the value(forAttribute:) method, and create a
MyDocument instance with it.
On the other hand, if no document was created yet, the createFile() method
has to generate the URL and create a new one. To build our document's
URL, we get the URL of the app's iCloud container with the
method provided by the FileManager object.
url(forUbiquityContainerIdentifier:)
The URL returned by this method is the container's root directory, to which
we must append the Documents directory and the file's name
(Documents/myfile.dat). With the URL ready, we create a MyDocument
object, assign an empty Data structure to the fileContent property to define
the document's initial content, and call the save() method to store it. If a
document in the URL already exists, we call this method with the
forOverwriting argument to modify it, otherwise, we call it with the forCreating
argument to create a new one. Internally, the MyDocument instance calls the
contents() method to get the document's content and creates the file inside
the container.
From the views, we must open the document, save the changes, and close
it. The following are the methods we need to include in the model for this
purpose.
The openDocument() method is called every time the user opens the view to
edit the document. In this method, we determine the URL, create the
instance of MyDocument again, open the document with the open() method,
read the content and return a string, so the view can show it on the screen.
The saveDocument() method performs the opposite process; it takes the text
inserted by the user, turns it into a Data structure with the data() method,
assigns it to the document, and saves the document in the container.
At the end, we also include a method called closeDocument(). The purpose of
this method is to close the document after the user finishes introducing
the changes.
With these methods, the model is ready. The next step is to provide the
views. And as always, they are very simple. The initial view only needs a
button to open the editor.
The button opens a sheet with a view to edit the document. We call it
EditDocumentView.
This view includes a TextEditor view to edit the document's content and a
button to save it. The editor's content is managed by a @State property
called inputText. When the view is loaded, we initiate a task that calls the
openDocument() method in the model and assigns the value returned by the
method to this property, so the TextEditor view is initialized with the
document's current content.
When the user taps the Save button to save the changes, we call the
saveDocument() method in the model to store the new value, and dismiss the
view. Notice that we have also implemented the onDisappear() modifier to
make sure that the document is always closed, no matter how or when the
view is dismissed.
In the previous example, we have worked with only one document, but
most applications allow users to create and manage all the documents they
need. The requirements for working with a single document or many are
the same. We must define a query and listen to the notifications to update
the data in our model and the interface. But this time we also need to
include a structure to store information about each document in the
container, so the views can show the list of documents available and users
can select the one they want to work with. The following model includes a
structure called FileInfo for this purpose.
init() {
metaData = NSMetadataQuery()
metaData.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
Task(priority: .high) {
let center = NotificationCenter.default
let name = NSNotification.Name.NSMetadataQueryDidFinishGathering
for await notification in center.notifications(named: name, object: metaData) {
if notification.name == name {
await getFiles()
}
}
}
Task(priority: .high) {
let center = NotificationCenter.default
let name = NSNotification.Name.NSMetadataQueryDidUpdate
for await notification in center.notifications(named: name, object: metaData) {
if notification.name == name {
let wrapper = NotificationWrapper(value: notification)
await updateFiles(notification: wrapper)
}
}
}
metaData.start()
}
}

The model in Listing 15-12 includes a new structure called FileInfo and a
@Published property called listOfFiles to store the instances that represent the
documents in the container. In addition to the
NSMetadataQueryDidFinishGathering notification, now we also listen to the
NSMetadataQueryDidUpdate notification to keep the files up to date. When this
notification is received, we execute a method called updateFiles(). This
method receives the Notification object produced by the notifications() method
to know the changes it needs to perform in the model. Notice that the
method is executed in the Main Actor, but the Notification object comes from
an asynchronous thread. This means that the object is not safe (it can
produce a data race). To be able to pass this value to the method without
getting warnings and errors from the compiler, we wrap it in a structure
called NotificationWrapper. (For more information on data races and the
Sendable protocol, read Chapter 9.)
The rest of the code in the observable object is the same as before, only
the methods change. For instance, when the
NSMetadataQueryDidFinishGathering notification is posted by the NSMetadataQuery
object, we execute a method called getFiles() to process the list of files
available in the container.
Listing 15-13: Processing the list of files in the container

@MainActor
func getFiles() {
if metaData.resultCount > 0 {
let files = metaData.results as! [NSMetadataItem]
for item in files {
let fileName = item.value(forAttribute: NSMetadataItemFSNameKey) as! String
if !listOfFiles.contains(where: { $0.name == fileName }) {
let documentURL = item.value(forAttribute: NSMetadataItemURLKey) as! URL
listOfFiles.append(FileInfo(name: fileName, url: documentURL))
}
}
listOfFiles.sort(by: { $0.name < $1.name })
}
}

NSMetadataQueryUpdateAddedItemsKey—This constant
retrieves an array of NSMetadataItem objects that represent the
documents added to the container.
NSMetadataQueryUpdateChangedItemsKey—This constant
retrieves an array of NSMetadataItem objects that represent the
documents that were modified.
NSMetadataQueryUpdateRemovedItemsKey—This constant
retrieves an array of NSMetadataItem objects that represent the
documents that were removed from the container.
The updateFiles() method in Listing 15-14 receives the wrapper with the
Notification object, so we can read the notification's userInfo property and
determine the type of updates performed by the user. If the value is of
type NSMetadataQueryUpdateRemovedItemsKey, we remove the deleted
documents from the listOfFiles array, but if the value is of type
NSMetadataQueryUpdateAddedItemsKey, we create a FileInfo structure to
represent the file and add it to the listOfFiles array as before.
Notice that to avoid simultaneous updates, we disable the NSMetadataQuery
object momentarily with the disableUpdates() method and enable it again
with the enableUpdates() method when the updates are over.
The remaining methods are those required by the views to process the
documents. For instance, we need the following two methods to create
and remove files from the container.
The createFile() method defines a URL with the file's name, creates an empty
document, and saves it in the container. On the other hand, the removeFiles()
method removes the files selected by the user from the container and the
corresponding FileInfo structures from the array. This last method receives
an IndexSet value with the indexes of the documents to be removed. This is
the value generated by the onDelete() modifier. We iterate through these
values with a for in loop, get the file's URL from the url property, and
remove it from the container with the removeItem() method and from the
listOfFiles array with the remove() method. Notice that the method is
asynchronous. This is required by the system. Every time we remove a file
from the ubiquitous container we must do it from a background thread.
And this is the reason why we modify the listOfFiles property from the Main
Actor. (Changes to the interface must always be performed from the main
thread.)
As before, the model also needs a few more methods to open, save, and
close the documents.
The model is ready, now we must define the views. For this example, we
need three views: one to list the documents available, one to let the user
add new documents, and another to edit them. The following are the
changes we need to introduce to the ContentView view.
This view creates a list with the values in the listOfFiles property, applies the
onDelete() modifier to the ForEach view to let the user delete a document,
and includes a button in the navigation bar to open a sheet to add new
documents. The view opened by the sheet() modifier is called CreateFileView.
Task(priority: .high) {
await appData.createFile(name: fileName)
dismiss()
}
}
}.disabled(buttonDisabled)
}.padding()
TextField("Insert name and extension", text: $inputFileName)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
.disableAutocorrection(true)
.padding()
Spacer()
}
}
}

This view includes a TextField view to let the user insert the name of the
document and a button to save it. When the user presses the Save button,
the code checks whether a file with that name already exists in the
listOfFiles array, disables the button to show to the user that the action is
being processed, and calls the createFile() method to add it to the container.
The ForEach loop in the ContentView view includes a NavigationLink view for the
rows. This navigation link opens the EditDocumentView view to allow the user
to edit the selected document. The following are the changes we need to
introduced to this view for our example.
This view receives a value of type FileInfo with the information of the file
selected by the user. As soon as the view is loaded, we initiate an
asynchronous task to open the document for this file and assign the
content to the TextEditor view so the user can see it and modify it.

Do It Yourself: Update the ApplicationData.swift file with the code in
Listing 15-12 and the methods in Listings 15-13, 15-14, 15-15, and
15-16. Update the ContentView view with the code in Listing 15-17.
Create a SwiftUI View file called CreateFileView.swift for the view in
Listing 15-18, and update the EditDocumentView view with the code in
Listing 15-19. Run the application simultaneously in two devices.
Press the Create File button to add a document. You should see the
document listed on both devices. Slide the row to the left and press
the Delete button. The document should be removed in both
devices.
15.4 CloudKit

CloudKit is a database system in iCloud. Using this system, we can store
structured data online with different levels of accessibility. The system
offers three types of databases to determine who has access to the
information.
As we did with the rest of the iCloud services, the first step to use CloudKit
is to activate it from the Signing & Capabilities panel.
And the Countries entity needs an attribute of type String called name and
a To-Many relationship called cities, as shown below.
Figure 15-16: Countries entity
There is one more requirement for the model to be ready to work with
CloudKit. We must select the Configuration (Figure 15-17, number 1) and
check the option Used with CloudKit in the Data Model Inspector panel
(Figure 15-17, number 2). This makes sure that if we create other
configurations later, the system knows which one must be synchronized
with CloudKit servers.
Once the application is configured to work with CloudKit and the Core Data
model is ready, we can work on our code. First, we must initialize the Core
Data stack as we did before but using the NSPersistentCloudKitContainer class
instead (see Listing 10-56).
@main
struct TestApp: App {
@StateObject var appData = ApplicationData()
And that's all it takes. From now on, every change introduced in the
Persistent Store is going to be uploaded to CloudKit and every device
running the application is going to be automatically synchronized. All that
is left is to design the views to display the objects or add new ones. Here is
the initial view for our example.
There is also nothing new in this view. We create a new Countries object
with the value inserted by the user when the Save button is pressed and
save the context with the save() method, but because the application is
connected to CloudKit the system automatically creates a record from the
Countries object and uploads it to CloudKit servers.
The following is the ShowsCitiesView view opened when a country is selected
by the user.
init(selectedCountry: Countries?) {
self.selectedCountry = selectedCountry
if selectedCountry != nil {
_listCities = FetchRequest(sortDescriptors: [SortDescriptor(\Cities.name, order:
.forward)], predicate: NSPredicate(format: "country = %@", selectedCountry!), animation:
.default)
}
}
var body: some View {
List {
ForEach(listCities) { city in
Text(city.name ?? "Undefined")
}
}
.navigationBarTitle(selectedCountry?.name ?? "Undefined")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Add City") {
openSheet = true
}
}
}
.sheet(isPresented: $openSheet) {
InsertCityView(country: selectedCountry)
}
}
}
struct ShowCitiesView_Previews: PreviewProvider {
static var previews: some View {
NavigationStack {
ShowCitiesView(selectedCountry: nil)
.environment(\.managedObjectContext, ApplicationData.preview.container.viewContext)
}
}
}

This view lists the cities with a List view, as we did before for the countries,
but because we only need to show the cities that belong to the selected
country, we initialize the @FetchRequest property wrapper with a false
predicate and then use the Countries object returned by the selectedCountry
property to create a new FetchRequest structure with a predicate that filters
the cities by country (see Listing 10-68).
The ShowCitiesView view also includes a button to let the user insert new
cities. The following is the view opened by the sheet() modifier when the
button is pressed.
Again, we just create a new Cities object with the value inserted by the user
when the Save button is pressed and the system takes care of creating the
record an uploading it to CloudKit.
The application shows the list of countries and cities stored in the
Persistent Store, and allows the user to insert new values, as previous Core
Data applications, but now all the information is uploaded to CloudKit
servers and automatically shared with other devices.
The CKRecord class offers properties to set or get the record's ID and other
attributes. The following are the most frequently used.
Because the values of a record are stored as key/value pairs, we can use
square brackets to read and modify them (as we do with dictionaries), but
the class also includes the following methods.
As illustrated in Figure 15-11, the Public database can only store records in
a default zone, but the Private and Shared databases can include custom
zones. In the case of the Private database, the custom zones are optional
(although they are required for synchronization, as we will see later).
Zones are like sections inside a database to separate records that are not
directly related. For example, we may have an app that stores locations,
like the names of countries and cities, but also allows the user to store a
list of Christmas gifts. In cases like this, we can create a zone to store the
records that include information about countries and cities and another
zone to store the records that include information about the gifts. The
CloudKit framework provides the CKRecordZone class to represent these
zones. The class includes an initializer to create custom zones and a type
method to get a reference to the zone by default.
CloudKit is an online service and therefore any task may take time to
process. For this reason, the CloudKit framework uses asynchronous
operations to access the information on the servers. An operation must be
created for every process we want to perform on a database, including
storing, reading, and organizing records.
These operations are like Swift asynchronous tasks but created from
classes defined in the Foundation framework. The CloudKit framework
includes its own subclasses of the Foundation classes to define operations.
There is a base class called CKDatabaseOperation, and then several subclasses
for every operation we need. Once an operation is defined, it must be
added to the CKDatabase object that represents the database we want to
modify. The CKDatabase class offers the following method for this purpose.
Although we can create single operations and assign them to the database,
as we will see later, the CKDatabase class also offers convenient methods to
generate and execute the most common. The following are the methods
available to process records.
The following are the methods provided by the CKDatabase class to process
zones.
Although the methods provided by the CKDatabase class are very convenient
and easy to implement, they only perform one request at a time. The
problem is that CloudKit servers have a limit on the number of operations
we can perform per second (currently 40 requests per second are allowed),
so if our application relies heavily on CloudKit, at one point some of the
requests might be rejected if we send them one by one. The solution is to
create operations that allow us to perform multiple requests at once. The
CloudKit framework defines three subclasses of the CKDatabaseOperation
class for this purpose. The CKModifySubscriptionsOperation class creates an
operation to add or modify subscriptions, the CKModifyRecordZonesOperation
class is used to add or modify record zones, and the CKModifyRecordsOperation
class is for adding and modifying records.
CKModifySubscriptionsOperation(subscriptionsToSave:
[CKSubscription], subscriptionIDsToDelete:
[CKSubscription.ID])—This initializer returns an operation that adds
or modifies one or more subscriptions. The subscriptionsToSave
argument is an array with the subscriptions we want to add or modify,
and the subscriptionIDsToDelete argument is an array with the IDs of
the subscriptions we want to delete from the server.
CKModifyRecordZonesOperation(recordZonesToSave:
[CKRecordZone], recordZoneIDsToDelete: [CKRecordZone.ID])
—This initializer returns an operation that adds or modifies one or
more record zones. The recordZonesToSave argument is an array with
the record zones we want to add or modify, and the
recordZoneIDsToDelete is an array with the IDs of the record zones
we want to delete from the server.
CKModifyRecordsOperation(recordsToSave: [CKRecord],
recordIDsToDelete: [CKRecord.ID])—This operation adds or
modifies one or more records. The recordsToSave argument is an
array with the records we want to add or modify, and the
recordIDsToDelete argument is an array with the IDs of the records
we want to delete from the server.
These operations must be initialized first and then added to the database
with the add() method of the CKDatabase class. Each initializer offers the
options to modify elements and remove them. If we only need to perform
one task, the other can be omitted with the value nil.
References

Records of different types are usually related. For example, along with
records of type Countries we may have records of type Cities to store
information about the cities of each country. To create these relationships,
records include references. References are objects that store information
about a connection between one record and another. They are created
from the Reference class defined inside the CKRecord class. The following are
its initializers.

CloudKit Dashboard


If we click on the CloudKit Database button, a panel is loaded to edit the
database. The panel includes an option at the top to select the container
(Figure 15-21, number 1), a bar on the left to edit the data and the schema,
and a bar on the right to show and edit the values.
The bar on the left includes a button to select from two configurations:
Development and Production (Figure 15-21, number 2). The Development
option shows the configuration of the database used during development.
This is the database we use as we develop our app. The Production option
shows the configuration of the database that we are going to deliver with
our app (the one that is going to be available to our users).
During development, we can store information in the database for testing.
Below the configuration option is the Data section (Figure 15-21, number
3) where we can edit the data stored by our application, including records,
zones, and subscriptions. The panel also offers an option on the right to
add records (Figure 15-21, number 4), and buttons to select the database
we want to access (Public, Private, or Shared), select the zone, and indicate
how we want to access the records.
From the Record Types option, we can add, modify, or delete record types
(Entities) and their values (Attributes). The option to add a new record type
is at the top of the panel (Figure 15-23, number 1), and the record types
already created are listed below (Figure 15-23, number 2).

Custom CloudKit Application

Using the tools introduced above, we can implement CloudKit on our own,
but there are several ways to do it. It all depends on the characteristics of
our application and what we want to achieve. An alternative is to centralize
all the logic in the model. For instance, the following is a possible
implementation of the application created before with Core Data to store
countries and cities.
struct Country {
var name: String?
var record: CKRecord
}
struct City {
var name: String?
var record: CKRecord
}
struct CountryViewModel: Identifiable {
var id: CKRecord.ID
var country: Country
init() {
let container = CKContainer.default()
database = container.privateCloudDatabase
Task(priority: .high) {
await readCountries()
}
}
func insertCountry(name: String) async {
let id = CKRecord.ID(recordName: "idcountry-\(UUID())")
let record = CKRecord(recordType: "Countries", recordID: id)
record.setObject(name as NSString, forKey: "name")
do {
try await database.save(record)
await MainActor.run {
let newCountry = Country(name: record["name"], record: record)
let newItem = CountryViewModel(id: record.recordID, country: newCountry)
listCountries.append(newItem)
listCountries.sort(by: { $0.countryName < $1.countryName })
}
} catch {
print("Error: \(error)")
}
}
func insertCity(name: String, country: CKRecord.ID) async {
let id = CKRecord.ID(recordName: "idcity-\(UUID())")
let record = CKRecord(recordType: "Cities", recordID: id)
record.setObject(name as NSString, forKey: "name")
do {
try await database.save(record)
await MainActor.run {
let newCity = City(name: record["name"], record: record)
let newItem = CityViewModel(id: record.recordID, city: newCity)
listCities.append(newItem)
listCities.sort(by: { $0.cityName < $1.cityName })
}
} catch {
print("Error: \(error)")
}
}
func readCountries() async {
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Countries", predicate: predicate)
do {
let list = try await database.records(matching: query, inZoneWith: nil, desiredKeys: nil,
resultsLimit: 0)
await MainActor.run {
listCountries = []
for (_, result) in list.matchResults {
if let record = try? result.get() {
let newCountry = Country(name: record["name"], record: record)
let newItem = CountryViewModel(id: record.recordID, country: newCountry)
listCountries.append(newItem)
}
}
listCountries.sort(by: { $0.countryName < $1.countryName })
}
} catch {
print("Error: \(error)")
}
}
func readCities(country: CKRecord.ID) async {
let predicate = NSPredicate(format: "country = %@", country)
let query = CKQuery(recordType: "Cities", predicate: predicate)
do {
let list = try await database.records(matching: query, inZoneWith: nil, desiredKeys: nil,
resultsLimit: 0)
await MainActor.run {
listCities = []
for (_, result) in list.matchResults {
if let record = try? result.get() {
let newCity = City(name: record["name"], record: record)
let newItem = CityViewModel(id: record.recordID, city: newCity)
listCities.append(newItem)
}
}
listCities.sort(by: { $0.cityName < $1.cityName })
}
} catch {
print("Error: \(error)")
}
}
}

We begin by defining two structures, Country and City, to store the name of
the country and city, and also a reference to the record downloaded from
the CloudKit database, and two more for our view model, CountryViewModel
and CityViewModel. These view models identify each value by the record ID
(CKRecord.ID) and include a computed property to return a string with the
name.
The observable object defines the @Published properties we need to store
the data locally and show it to the user. The listCountries property is an array
with the list of countries already inserted in the database, and the listCities
property is another array with the list of cities available for a specific
country. Another property included in this class is database. This property
stores a reference to the CloudKit's database, so we can access it from
anywhere in the code. The property is initialized in the init() method with a
reference to the Private Database. (We use the private database because
we only want the user to be able to share the data between his or her own
devices.)
The observable object also includes methods to add and read records. For
instance, the insertCountry() and insertCity() methods are going to be called
from the views when the user inserts a new country or city. Their task is to
create the records and upload them to CloudKit. The process begins by
defining a record ID, which is a unique value that identifies each record. For
the countries, we use the string "idcountry" followed by a random value
generated by the UUID() function. Using this ID, we create a CKRecord object
of type Countries, then add a property called "name" with the value
received by the method, and finally save it in CloudKit servers with the
save() method of the CKDatabase object. This method generates an operation
that communicates with the servers asynchronously. If there is no error, we
add the record to the listCountries property, which updates the views and the
screen.
The method to add a city is the same, with the exceptions that we must
define the type of records as Cities and add an extra attribute to the record
with a reference to the country the city belongs to. For this purpose, we
create a Reference object with the country's record ID received by the
method and an action of type deleteSelf, so when the record of the country is
deleted, this record is deleted as well.
Next are the methods we need to implement to read the countries and
cities already stored in the database. In the readCountries() method, we
define a predicate with the TRUEPREDICATE keyword and a query for
records of type Countries. The record type asks the server to only look for
records of type Countries, and the TRUEPREDICATE keyword determines
that the predicate will always return true, so we get back all the records
available. If the query doesn't return any errors, we get the records from
the matchResults value and add them to the listCountries array to update the
views.
The readCities() method is very similar, except that this time we are getting
the list of cities that belong to the country selected by the user. (The view
that shows the cities only opens when the user taps on a row to select a
country.) The rest of the process is the same. We get the records that
represent the cities, create the CityViewModel structures with them, and
store them in the listCities array.
For the interface, we need a total of four views: a view to show the list of
countries, a view to allow the user to insert a new country, a view to show
the list of cities that belong to the selected country, and another view to
allow the user to insert a new city. The following is the initial view.
Task(priority: .high) {
await appData.insertCountry(name: text)
dismiss()
}
}
}.disabled(buttonDisabled)
}
Spacer()
}.padding()
}
}
struct InsertCountryView_Previews: PreviewProvider {
static var previews: some View {
InsertCountryView().environmentObject(ApplicationData())
}
}

This view includes a TextField view to insert the name of the country and a
button to save it in the database. When the button is pressed, we call the
insertCountry() method in the model. The method creates the record with the
value inserted by the user and calls the save() method on the database to
store it in CloudKit servers.
Next is the view necessary to show the list of cities available for each
country. The view is called ShowCitiesView and opens when the user taps on a
row in the initial view to select a country.
Task(priority: .high) {
await appData.insertCity(name: text, country: self.country)
dismiss()
}
}
}.disabled(buttonDisabled)
}
Spacer()
}.padding()
}
}
struct InsertCityView_Previews: PreviewProvider {
static var previews: some View {
InsertCityView(country: CKRecord.ID(recordName: "Test"))
.environmentObject(ApplicationData())
}
}

The view receives the country's record ID to know which country the city
belongs to, and then calls the insertCity() method in the model with the
name inserted by the user and this ID to create a Cities record connected
to that Countries record in the CloudKit database.
The application is ready. When the user inserts a new value, the code
creates a record and uploads it to CloudKit servers, but if we stop and start
the application again, the countries are not shown on the screen anymore.
This is because we haven't defined the required indexes.
CloudKit automatically creates indexes for every key we include in the
records, except for the record’s identifier. Therefore, when we query the
Cities records by their country attribute to get the cities that belong to the
country selected by the user, CloudKit knows how to find and return those
records, but when we try to retrieve the Countries records without a
predicate, CloudKit tries to fetch them by the record identifiers and fails
because there is no index associated to that attribute (called recordName).
To create an index for this attribute, we need to go to the dashboard, click
on the Indexes option in the Schema section, and click on the Countries
record type to modify it.
When we click on a record, the panel shows the list of indexes available. By
default, the Countries records contains three indexes for the name
attribute, but no index for the record's identifier.
To add an index, we must press the Add Basic Index button at the bottom
of the list. The panel opens a form to create a new index.
There are three types of indexes: Queryable (it can be included in a query),
Searchable (it can be searched), and Sortable (it can be sorted). By default,
all these indexes are associated to custom attributes but not to the record's
identifier. When we query the database from the readCountries() method in
the model, we do not specify any field in the predicate and therefore the
system fetches the records by their identifiers, which is described in the
database as recordID (recordName). For this reason, to retrieve the
countries in our example, we must add a Queryable index to the
recordName field of the Countries record type, as shown in figure 15-25.
Once we select the recordName field and the Queryable index, we can
press the Save button to save the changes. Now, if we run the application
again from Xcode, the records added to the database are shown on the
screen.
Records may also include files, and the files may contain anything from
pictures to sound or even videos. To add a file to a record, we must create
an asset with the CKAsset class. The class includes the following initializer.
The assets are added to a record with a key, as any other value. For our
example, we are going to store a picture in the record of every city and add
a view to show the picture when the city is selected.
In the new insertCity() method introduced in Listing 15-31, we get the URL of
an image included in the project called Toronto, create a CKAsset object
with it, and assign the object to the record of every city with the "picture"
key. Now, besides the name, a file with this image will be stored for every
city inserted by the user. To get the asset back, we must add a computed
property to the view model, as shown next.
How to read the assets stored in the record depends on the type of
content managed by the asset. In this example, we must use the asset's
URL to create a UIImage object to show the image to the user. For this
purpose, the cityPicture property introduced in Listing 15-32 reads the value
in the record with the "picture" key, casts it as a CKAsset object, and gets
the asset's URL from the fileURL property. If the process is successful, we
create a UIImage object with the image in this URL and return it, otherwise,
we return a UIImage object with a placeholder image (nopicture).
The following are the modifications we must introduce to the ShowCitiesView
view to provide the NavigationLink view required for the user to be able to
select a city and open a view to see the city's picture.
This view receives the information about the selected city through the
selectedCity property. From this property, we get the city's picture and name
and show them to the user. As a result, every time the user selects a city,
the asset is turned into an image and displayed on the screen.
The previous examples fetch the records available and show them on the
screen every time the app is launched. This means that records added to
the database from another device will not be visible until the app is
launched again. This is not the behavior expected by the user. When
working with applications that store information online, users expects the
information to be updated as soon as it becomes available. To provide this
feature, CloudKit uses subscriptions.
Subscriptions are queries stored by our application in CloudKit servers.
When a change occurs in the database, the query detects the modification
and triggers the delivery of a Remote Notification from the iCloud servers
to the copy of the app that registered the subscription.
Database subscriptions are created from the CKDatabaseSubscription class (a
subclass of a generic class called CKSubscription). The class includes the
following initializer.
application(UIApplication, didReceiveRemoteNotification:
Dictionary, fetchCompletionHandler: Block)—This method is
called by the application on its delegate when a Remote Notification is
received. The didReceiveRemoteNotification argument is a dictionary
with information about the notification, and the
fetchCompletionHandler argument is a closure that we must execute
after all the custom tasks are performed. The closure must be called
with a value that describes the result of the operation. For this
purpose, UIKit offers the UIBackgroundFetchResult enumeration with the
values newData (new data was downloaded), noData (no data was
downloaded), and failed (the app failed to download the data).
CKNotification(fromRemoteNotificationDictionary:
Dictionary)—This initializer creates a CKNotification object from the
information included in the notification (the value of the userInfo
parameter in the delegate method).
Of course, the protocol methods defined in Listing 15-35 are only called if
we assigned the class as the app delegate with the
@UIApplicationDelegateAdaptor property wrapper from the App structure.
@main
struct TestApp: App {
@UIApplicationDelegateAdaptor(CustomAppDelegate.self) var appDelegate
@StateObject var appData = ApplicationData.shared
Listing 15-37: Defining the properties to control the subscription and the
custom zone

class ApplicationData: ObservableObject {
@AppStorage("subscriptionSaved") var subscriptionSaved: Bool = false
@AppStorage("zoneCreated") var zoneCreated: Bool = false
@AppStorage("databaseToken") var databaseToken: Data = Data()
@AppStorage("zoneToken") var zoneToken: Data = Data()
private init() {
let container = CKContainer.default()
database = container.privateCloudDatabase
Task(priority: .high) {
await readCountries()
}
}

do {
try await database.save(newSubscription)
await MainActor.run {
subscriptionSaved = true
}
} catch {
print("Error: \(error)")
}
}
if !zoneCreated {
let newZone = CKRecordZone(zoneName: "listPlaces")
do {
try await database.save(newZone)
await MainActor.run {
zoneCreated = true
}
} catch {
print("Error: \(error)")
}
}
}

Listing 15-39: Initiating the process to get the updates from the server

func checkUpdates(finishClosure: @escaping (UIBackgroundFetchResult) -> Void) {
Task(priority: .high) {
await configureDatabase()
downloadUpdates(finishClosure: finishClosure)
}
}

CKFetchDatabaseChangesOperation(previousServerChangeT
oken: CKServerChangeToken?)—This initializer creates an
operation to fetch changes from a database. The argument is a token
that determines which changes were already fetched. If we specify a
token, only the changes that occurred after the token was created are
fetched.
CKFetchRecordZoneChangesOperation(recordZoneIDs:
[CKRecordZone.ID], configurationsByRecordZoneID:
Dictionary)—This initializer creates an operation to download
changes from a database. The recordZoneIDs argument is an array
with the IDs of all the zones that present changes, and the
configurationsByRecordZoneID argument is a dictionary with
configuration values for each zone. The dictionary takes
CKRecordZone.ID objects as keys and options determined by an object
of the ZoneConfiguration class included in the
CKFetchRecordZoneChangesOperation class. The class includes three
properties to define the options: desiredKeys (array of strings with the
keys we want to retrieve), previousServerChangeToken (CKServerChangeToken
object with the current token), and resultsLimit (integer that determines
the number of records to retrieve).
CloudKit servers use tokens to know which changes were already sent to
every instance of the app, so the information is not downloaded twice
from the same device. If a device stores or modifies a record, the server
generates a new token, so the next time a device accesses the servers only
the changes introduced after the last token was created will be
downloaded.
In the process depicted in Figure 15-27, the app in Device 1 stores a new
record in the server (Record 1). To report the changes, the server generates
a new token (A). When the app in Device 2 connects to the server, the
server detects that this device does not have the latest token, so it returns
Record 1 and the current token (A) to update the state in this device. If
later the user decides to create a new record from Device 2 (Record 2), a
new token will be created (B). The next time Device 1 connects to the
server, it will find that its token is different from the server's token, so it
will download the modifications inserted after token A.
Tokens are great because they allow us to only get the latest changes, but
this process is not automatic, we are responsible of storing the current
tokens and preserve the state of our app. The server creates a token for
the database and a token for each of the custom zones. For our example,
we need two tokens: one to keep track of the changes in the database and
another for the custom zone created by the configureDatabase() method. To
work with these values, we are going to use two variables called
changeToken, for the database token, and fetchChangeToken, for the token of
our custom zone, and we are going to store them permanently with the
@AppStorage properties defined before in the model (databaseToken and
zoneToken). All this process is performed by the downloadUpdates() method.
do {
try await database.save(record)
await MainActor.run {
let newCountry = Country(name: record["name"], record: record)
let newItem = CountryViewModel(id: record.recordID, country: newCountry)
listCountries.append(newItem)
listCountries.sort(by: { $0.countryName < $1.countryName })
}
} catch {
print("Error: \(error)")
}
}
}
func insertCity(name: String, country: CKRecord.ID) async {
await configureDatabase()
If the status of the iCloud account changes while the app is running, the
system posts a notification that we can use to perform updates and
synchronization tasks.
do {
let container = CKContainer.default()
let status = try await container.accountStatus()
if status != CKAccountStatus.available {
print("iCloud Not Available")
return
}
} catch {
print("Error: \(error)")
return
}
let text = name.trimmingCharacters(in: .whitespaces)
if !text.isEmpty {
let zone = CKRecordZone(zoneName: "listPlaces")
let id = CKRecord.ID(recordName: "idcountry-\(UUID())", zoneID: zone.zoneID)
let record = CKRecord(recordType: "Countries", recordID: id)
record.setObject(text as NSString, forKey: "name")
do {
try await database.save(record)
await MainActor.run {
let newCountry = Country(name: record["name"], record: record)
let newItem = CountryViewModel(id: record.recordID, country: newCountry)
listCountries.append(newItem)
listCountries.sort(by: { $0.countryName < $1.countryName })
}
} catch {
print("Error: \(error)")
}
}
}

This example checks the status of the account and prints a message on the
console if an error occurs or the status is other than available. If an error
occurs, the code returns from the function without letting the user insert
the new record.
In the last example, we just checked whether an error occurred or not and
proceeded accordingly, but we can also identify the type of error returned
by the operation. Errors are structures that conform to the Error protocol.
Every time we want to read an error, we must cast it to the right type. In
CloudKit, the errors are of type CKError, a structure that includes the
following property to return the error code.
A representable view has a flexible size by default, but we can use SwiftUI
modifiers to change it. In this example, we use the frame() modifier to assign
a fixed width and height.
A UIView object creates an empty view, but we can also implement views
that take user's input, such as input fields, switches, and more. To pass
values from the SwiftUI view to the UIKit view, we use the updateUIView()
method, but if we want to send values from the UIKit view to the SwiftUI
interface, we must implement the makeCoordinator() method. From this
method, we must create an instance of a coordinator object and return it.
A coordinator is an object that can send information back from the UIKit
view to the SwiftUI interface, usually by modifying Binding properties. How
to process these values depends on the type of UIKit view we are working
with. For instance, a UITextView view creates an input field for the user to
type multiple lines of text, like the TextEditor view in SwiftUI. This view
reports changes by calling delegate methods. Therefore, to get the text
inserted by the user in a UITextView view and process it in SwiftUI, we must
create a coordinator class that conforms to the UITextViewDelegate protocol
and implements its methods. The following example illustrates how to
create a UIViewRepresentable structure to work with this class.
Listing 16-3: Sending and receiving values from the SwiftUI view

import SwiftUI
init(input: Binding<String>) {
self._inputCoordinator = input
}
func textViewDidChange(_ textView: UITextView) {
inputCoordinator = textView.text
}
}

This view defines a @State property called inputText, a Text view to show its
value, a button to replace the current value with an empty string, and an
instance of our TextInput view. This view receives a reference to the inputText
property, so the property is connected to the input property in the
representable view and we can pass values back and forth.
When the user types or removes a character from the text view, the
representable view calls the textViewDidChange() method in the coordinator
and the method assigns the current value in the text view to the
inputCoordinator property, and hence to the input property. This means that
the value is now available in the view's @State property and the Text view
can show it on the screen.
On the other hand, when the user presses the Clear button, we assign an
empty string to the @State property, the system executes the updateUIView()
method in the representable view, and the value of the Binding property
connected to the @State property is assigned to the view, so the text view is
cleared.
Do It Yourself: Select the File option from the menu at the top of the
screen to create a new file. Click on the Cocoa Touch Class icon in the
iOS section to create a UIKit file. Select the UIViewController class from
the Subclass option. Insert the name DetailViewController and press
Next to save it. Update the DetailViewController class with the code in
Listing 16-5. Create a Swift file called MyViewController.swift for the
code in Listing 16-6. Update the ContentView view with the code in
Listing 16-7. Run the application on the iPhone simulator. Press the
button to open the UIKit view controller. We will see some practical
examples on how to implement Representable Views and
Representable View Controllers in the following chapters.
CHAPTER 17 - WEB
17.1 Web

Apps can allow the user to access the web, but there are different ways to
do it. We can provide links for the user to open a document in the browser,
embed a predefined browser into the app's interface, or load data in the
background, process it, and show the result to the user.
Links

A link is a text or an image associated with a URL that indicates the location
of a document. When the user clicks or taps the link, the document is
opened. Links were designed for the web, but we can add them to our
applications and let the system decide where to open the document (a
browser or another app). SwiftUI includes the Link view to create them.
In this example, we have defined the URL in code, but sometimes the URL
is provided by the user or taken from another document. In cases like this,
the URL may contain characters that are not allowed and can cause the
location to be impossible to identify. To make sure that the URL is valid, we
must turn unsafe characters into percent-encoding characters. These are
characters represented by the % sign followed by an hexadecimal number.
The String structure includes the following method for this purpose.
addingPercentEncoding(withAllowedCharacters:
CharacterSet)—This method returns a string with all the characters
in the set specified by the argument replaced by percent-encoded
characters. The withAllowedCharacters argument is a structure with
type properties to create instances that represent common sets. The
ones available for URLs are urlFragmentAllowed, urlHostAllowed,
urlPasswordAllowed, urlPathAllowed, urlQueryAllowed, and urlUserAllowed.
This method is implemented by the NSString class, but we can use it from
any instance of the String structure. This means that all we need is to apply
the method to the URL we want to check and assign that URL to the Link
view. The problem is that this view takes a URL that is ready to be
processed, so we must first check the value from a computed property or a
method. To simplify the process, the environment includes a property
called openURL that returns a method we can use to open a URL, and
therefore we can include it in a control or any operation we want. For
instance, the following example implements a Button view that replaces
invalid characters in the URL with percent-encoded characters and then
executes the openURL() method to open it.
In this example, we process a URL that we know it works, but this is not
always the case. URLs are usually taken from external sources or provided
by the user. In cases like this, we not only have to encode the values with
the addingPercentEncoding() method but also check that all the components of
the URL are in place. For instance, if the user only writes the domain
(www.formasterminds.com) without the protocol (https), we must build
the full URL before trying to open it. To read, create, or modify URL
components, the Foundation framework defines the URLComponents
structure. The structure includes the following initializer.
string—This property returns a string with the URL built from the
values of the components.
In the following example, we allow the user to insert a URL, but we assign
the https protocol to the URL to make sure it is always included.
Listing 17-3: Encoding custom URLs

struct ContentView: View {
@Environment(\.openURL) var openURL
@State private var searchURL = ""
The URLComponents structure takes a string with the URL, extracts the
components, and assigns them to the structure's properties, so we can
read or modify them. In this example, we assign the "https" string to the
scheme property to make sure the URL is valid and can be processed by the
system. Once the components are ready, we get the full URL from the string
property, replace invalid characters with percent-encoded characters, and
open it.
Links provide access to the web from our app, but they open the document
in an external application. Considering how important it is for our
application to capture the user’s attention, Apple includes a framework
called SafariServices. This framework allows us to incorporate the Safari
browser into our app to offer a better experience to our users. The
framework includes the SFSafariViewController class to create a view
controller that incorporates its own view to display web pages and tools for
navigation.
This view defines a @State property of type URL that is initialized with the
URL https://www.formasterminds.com. When the button is pressed, a
SafariBrowser view is initialized with this value, the browser is opened in a
sheet, and the website is loaded.
When the user scrolls the page, the controller collapses the bars to make
room for the content. This makes difficult for the user to dismiss the view
or access the tools. If we think that it is more appropriate for our app to
always keep the bars at the original size, we can initialize the controller
with a Configuration object. This class is defined inside the
SFSafariViewController class and includes the following property to configure
the bars.
Once the Configuration object is created, we can configure the property and
assign it to the Safari View Controller from the controller’s initializer.
init(disableCoordinator: Binding<Bool>) {
self._disableCoordinator = disableCoordinator
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
disableCoordinator = true
}
}

In the view, we need to define a @State property to store the Boolean value
and implement the disable() modifier on the Button view to enable or disable
the button depending on this value.
Listing 17-9: Disabling the button from the Safari View Controller delegate

struct ContentView: View {
@State private var searchURL: URL = URL(string: "https://www.formasterminds.com")!
@State private var openSheet: Bool = false
@State private var disableButton: Bool = false
For some applications, the options for customization included in the Safari
view controller are not enough. To provide more alternatives, Apple offers
the WebKit framework. With this framework we can display web content
within a view. The view is defined by a subclass of the UIView class called
WKWebView. The class provides the following properties and methods to
manage the content.
A WebKit view can report the state of the content through a delegate. For
this purpose, the framework defines the WKNavigationDelegate protocol. The
following are some of the methods included in this protocol.
webView(WKWebView, decidePolicyFor:
WKNavigationAction, decisionHandler: Closure)—This method
is called on the delegate to determine if the view should process a
request. The decidePolicyFor argument is an object with information
about the request, and the decisionHandler argument is a closure
that we must execute to report our decision. The closure takes a value
of type WKNavigationActionPolicy, an enumeration with the properties
cancel and allow.
webView(WKWebView, didStartProvisionalNavigation:
WKNavigation!)—This method is called on the delegate when the
view begins loading new content.
webView(WKWebView, didFinish: WKNavigation!)—This
method is called on the delegate when the view finishes loading the
content.
webView(WKWebView, didFailProvisionalNavigation:
WKNavigation!, withError: Error)—This method is called on the
delegate when an error occurs loading the content.
webView(WKWebView,
didReceiveServerRedirectForProvisionalNavigation:
WKNavigation!)—This method is called on the delegate when the
server redirects the navigator to a different destination.
The WebKit view is a UIKit view and therefore we must use the
UIViewRepresentable protocol to create it. Once the representable view is
defined, the process to load a website in a WebKit view is simple; we
provide the URL, create a request, and ask the view to load it.
This example prepares the request with the URL received from the SwiftUI
interface and loads the website with the load() method. Because we always
load the same website, the view just has to define the URL and pass it to
the WebView instance.
init() {
webView = WebView(inputURL: $contentData.inputURL)
}
var body: some View {
VStack {
HStack {
TextField("Insert URL", text: $contentData.inputURL)
.autocapitalization(.none)
.disableAutocorrection(true)
Button("Load") {
let text = contentData.inputURL.trimmingCharacters(in: .whitespaces)
if !text.isEmpty {
webView.loadWeb(web: text)
}
}
}.padding(5)
webView
}
}
}

In this example, we include a property called webView to store a WebView
structure. The property is initialized with a new instance and then used to
call methods on the structure and present the view on the screen.
The URL inserted by the user is stored in a property called inputURL, which
is passed to the WebView structure. This is to be able to update the value of
the text field every time the user navigates to a new page. Notice that to
be able to pass the inputURL property to the view in the initializer, we had
to declare it as a @Published property inside an observable object.
The WebView structure for this example has to create the WKWebView view
and implement the methods to load new URLs and keep the views
updated.
Listing 17-13: Updating the WKWebView with the URLs inserted by the user

import SwiftUI
import WebKit
init(input: Binding<String>) {
self._inputURL = input
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
if let webURL = webView.url {
inputURL = webURL.absoluteString
}
}
}

In the app we have built so far, the user can visit any URL and navigate by
clicking on the links, but our interface doesn't offer the possibility to move
back or forward in the navigation history. The WKWebView class offers
several methods to control the content. For instance, there is the goBack()
method to go back to the previous page, the goForward() method to go to
the page we came back from, and the reload() method to refresh the page.
To execute these methods, we are going to add three buttons below the
navigation bar.
Listing 17-14: Providing buttons for navigation

import SwiftUI
init() {
webView = WebView(inputURL: $contentData.inputURL, backDisabled:
$contentData.backDisabled, forwardDisabled: $contentData.forwardDisabled)
}
var body: some View {
VStack {
HStack {
TextField("Insert URL", text: $contentData.inputURL)
Button("Load") {
let text = contentData.inputURL.trimmingCharacters(in: .whitespaces)
if !text.isEmpty {
webView.loadWeb(web: text)
}
}
}.padding(5)
HStack {
Button(action: {
webView.goBack()
}, label: {
Image(systemName: "arrow.left.circle")
.font(.title)
}).disabled(contentData.backDisabled)
Button(action: {
webView.goForward()
}, label: {
Image(systemName: "arrow.right.circle")
.font(.title)
}).disabled(contentData.forwardDisabled)
Spacer()
Button(action: {
webView.refresh()
}, label: {
Image(systemName: "arrow.clockwise.circle")
.font(.title)
})
}.padding(5)
webView
}
}
}

This view defines two more @Published properties in the observable object
to determine whether the back and forward buttons should be enabled or
not. When the view is displayed for the first time, the buttons should be
disabled because only one document was loaded into the view, but after a
new document is loaded, we have to enable the buttons to let the user go
back and forth in the navigation history. For this purpose, we must pass the
properties to the WebView structure and modify their values from the
coordinator every time a document is loaded.
This code adds three methods to the WebView structure to perform the
actions selected by the user (moving backward, forward, or refreshing the
page). In the webView(WKWebView, didFinish:) method, we update the URL in
the text field, as before, but also modify the state of the buttons with the
values of the canGoBack and canGoForward properties, so they are only
enabled when there is a page to open.

Do It Yourself: Update the ContentView.swift file with the code in
Listing 17-14 and the WebView.swift file with the code in Listing 17-
15. Run the application on the iPhone simulator. Search a term in
Google. Click on a link and press the back button. The view should go
back to the previous page.
The Safari view controller and the WebKit view were designed to show
content to the user, but the capacity to integrate that content with our app
is limited. Sometimes all we need is to extract a piece of information from
a document or process the data instead of showing the entire content as it
is. In cases like this, we must load the document in the background and
analyze it to extract only what we need.
Foundation includes a group of classes to get the content referenced by a
URL. The main class is called URLSession. This class creates a session that
manages an HTTP connection to obtain data, and download or upload files.
The following are some of the properties and initializers provided by the
class to create the session.
The following are the methods defined by the class to upload data and
files.
A standard session like the one we used in this example comes with a
configuration by default that is suitable for most situations, but a custom
session requires its own configuration. To configure a session, Foundation
provides a class called URLSessionConfiguration. The following is the type
property we can use to get a configuration object with values by default.
Working with custom sessions only requires us to change how the session
is initialized, but the rest of the code remains the same.
init() {
Task(priority: .high) {
await loadJSON()
}
}
func loadJSON() async {
let session = URLSession.shared
let webURL = URL(string: "https://jsonplaceholder.typicode.com/posts")
do {
let (data, response) = try await session.data(from: webURL!)
if let resp = response as? HTTPURLResponse {
let status = resp.statusCode
if status == 200 {
let decoder = JSONDecoder()
if let posts = try? decoder.decode([Post].self, from: data) {
await MainActor.run {
listOfPosts = posts
}
}
} else {
print("Error: \(status)")
}
}
} catch {
print("Error: \(error)")
}
}
}

As we have learned in Chapter 10, to decode a JSON document we must
define a structure that matches the JSON values one by one. The
https://jsonplaceholder.typicode.com/posts URL returns a list of posts,
each one with four values: an integer with the user's identifier, another
integer with the post's identifier, a string with the title, and a string with
the message. To store these values, the model in Listing 17-20 defines the
Post structure. The structure conforms to the Codable protocol to be able to
decode it, and to the Identifiable protocol to be able to list the instances with
a List view.
The process to download the document is the same as before. We get the
session, call the data() method on it, decode the data into an array of Post
structures with a JSONDecoder object, and store the values in the listOfPosts
property to update the view. Because the document is downloaded when
the model is initialized, all we have to do in the view is to list the values.
SwiftUI includes the PhotosPicker structure to generate a view that allows the
user to select one or multiple pictures from the Photo Library. The
following is the view's initializer.
Because it may take time to retrieve the items, the picker does not return
the images or videos directly, it returns a reference to the items that we
can use to retrieve them later. The framework defines the PhotosPickerItem
structure for this purpose. The structure includes the following property
and method to access the media.
The PhotosPicker structure and all the data types required to read and
retrieve items from the library are defined in the PhotosUI framework, that
we must import along with SwiftUI. Another requirement of the structure
is a state property to store the selected items. If we want to allow the user
to select multiple items, the property must store an array of PhotosPickerItem
structures, but if we want the user to select only one item, the property
only needs to store one optional PhotosPickerItem structure, as shown next.
Most of the arguments in the PhotosPicker initializer are optional. For this
example, we only need to tell the picker where to store the references to
the selected items, the type of items we want to show to the user (images),
and where to get them (the shared library). The PhotosPicker structure
creates a button that opens a view to select the items when pressed, so we
add it to the navigation bar.
When an item is selected, a reference is stored in the state property. This
means that we can monitor that property for changes. In this example, we
use the onChange() modifier. If a new image is selected, we start an
asynchronous task to call the loadTransferable() method on the selected item.
This method loads the image, turns it into a Data structure, and returns it. If
the process is successful, we use the data to initialize a UIImage object and
assign it to a picture property to show it on the screen.
func removeDeselectedItems() {
listPictures = listPictures.filter { value in
if selected.contains(where: { $0.itemIdentifier == value.id }) {
return true
} else {
return false
}
}
}
func addSelectedItems() {
for item in selected {
Task(priority: .background) {
if let data = try? await item.loadTransferable(type: Data.self) {
if let id = item.itemIdentifier, let image = UIImage(data: data) {
if !listPictures.contains(where: { $0.id == id }) {
let newPicture = ItemsData(id: id, image: image)
await MainActor.run {
listPictures.append(newPicture)
}
}
}
}
}
}
}
}

The model includes two @Published properties, one to store an array of
ItemsData structures to supply the currently selected images to the views,
and one with an array of PhotosPickerItem structures for the PhotosPicker view
to store the references of the items the user selects from the Photo Library.
There are also two methods in the model: removeDeselectedItems() and
addSelectedItems(). Both will be executed from the view every time the user
modifies the selection (Every time the value of the selected property
changes). The removeDeselectedItems() method iterates through the items in
the listPictures array to check which ones are still selected by the user, so any
item that has been deselected is not included on the list anymore. On the
other hand, the addSelectedItems() method adds to the listPictures array the
items that the user has added to the selection. The view can now use the
listPictures array to show the selected images on the screen, and call these
methods every time the selection is modified.
let guides = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationStack {
ScrollView {
LazyVGrid(columns: guides) {
ForEach(appData.listPictures) { item in
Image(uiImage: item.image)
.resizable()
.scaledToFit()
}
}
}.padding()
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Deselect") {
appData.selected = []
}
}
ToolbarItem(placement: .navigationBarTrailing) {
PhotosPicker(selection: $appData.selected, maxSelectionCount: 4, selectionBehavior:
.ordered, matching: .images, photoLibrary: .shared()) { Text("Select Photos") }
}
}
.onChange(of: appData.selected) { items in
appData.removeDeselectedItems()
appData.addSelectedItems()
}
}
}
}

One of the most common uses of mobile devices is to take and store
photos, and that is why no device is sold without a camera anymore.
Because of how normal it is for an application to access the camera and
manage pictures, UIKit offers a controller with built-in functionality that
provides all the tools necessary for the user to take pictures and record
videos. The class to create this controller is called UIImagePickerController. The
following are the properties included in this class for configuration.
The UIImagePickerController class creates a new view where the user can take
pictures or record videos. After the image or the video are created, the
view must be dismissed, and the media processed. The way our code gets
access to the media and knows when to dismiss the view is through a
delegate that conforms to the UIImagePickerControllerDelegate protocol. The
protocol includes the following methods.
imagePickerController(UIImagePickerController,
didFinishPickingMediaWithInfo: Dictionary)—This method is
called on the delegate when the user finishes taking the image or
recording the video. The second argument contains a dictionary with
the information about the media. The values in the dictionary are
identified with properties of the InfoKey structure included in the
UIImagePickerController class. The properties available are cropRect,
editedImage, imageURL, livePhoto, mediaMetadata, mediaType, mediaURL, and
originalImage.
imagePickerControllerDidCancel(UIImagePickerController)—
This method is called on the delegate when the user cancels the
process.
init() {
ImagePickerView = ImagePicker(path: $contentData.path, picture: $contentData.picture)
}
var body: some View {
NavigationStack(path: $contentData.path) {
VStack {
HStack {
Spacer()
NavigationLink("Get Picture", value: "Open Picker")
}.navigationDestination(for: String.self, destination: { _ in
ImagePickerView
})
Image(uiImage: contentData.picture ?? UIImage(named: "nopicture")!)
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.clipped()
Spacer()
}.padding()
}.statusBarHidden()
}
}

In the previous example, we show the picture on the screen, but we can
store it in a file or in the Core Data's Persistent Store. An alternative,
sometimes useful when working with the camera, is to store the picture in
the device’s Photo Library so that it is accessible to other applications. The
UIKit framework offers two functions to store images and videos.
UIImageWriteToSavedPhotosAlbum(UIImage, Any?,
Selector?, UnsafeMutableRawPointer?)—This function adds the
image specified by the first argument to the camera roll. The second
argument is a reference to the object that contains the method we
want to execute when the process is over, the third argument is a
selector that represents that method, and the last argument is an
object with data to pass to the method.
UISaveVideoAtPathToSavedPhotosAlbum(String, Any?,
Selector?, UnsafeMutableRawPointer?)—This function adds the
video to the camera roll at the path indicated by the first argument.
The second argument is a reference to the object that contains the
method we want to execute when the process is over, the third
argument is a selector that represents that method, and the last
argument is an object with additional data for the method.
init() {
ImagePickerView = ImagePicker(path: $contentData.path, picture: $contentData.picture)
}
var body: some View {
NavigationStack(path: $contentData.path) {
VStack {
HStack {
Button("Share Picture") {
showAlert = true
}.disabled(contentData.picture == nil ? true : false)
Spacer()
NavigationLink("Get Picture", value: "Open Picker")
}.navigationDestination(for: String.self, destination: { _ in
ImagePickerView
})
.alert("Save Picture", isPresented: $showAlert, actions: {
Button("Cancel", role: .cancel, action: {
showAlert = false
})
Button("YES", role: .none, action: {
if let picture = contentData.picture {
UIImageWriteToSavedPhotosAlbum(picture, nil, nil, nil)
}
})
}, message: { Text("Do you want to store the picture in the Photo Library?") })
The process is the same as before. The image picker controller allows the
user to take a picture and then calls the delegate method to process it. The
picture is assigned to the picture property to display it on the screen, but
now we have an additional button to save the picture to the Photo Library.
Share links are frequently used to share text, but they can share any type
of value we want as long as it conforms to the Transferable protocol. For
instance, we can share the picture taken by the camera.
init() {
ImagePickerView = ImagePicker(path: $contentData.path, picture: $contentData.picture)
}
var body: some View {
NavigationStack(path: $contentData.path) {
VStack {
HStack {
if let picture = contentData.picture {
let photo = Image(uiImage: picture)
ShareLink("Share Picture", item: photo, preview: SharePreview("Photo", image:
photo))
}
Spacer()
NavigationLink("Get Picture", value: "Open Picker")
}.navigationDestination(for: String.self, destination: { _ in
ImagePickerView
})
Image(uiImage: contentData.picture ?? UIImage(named: "nopicture")!)
.resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.clipped()
Spacer()
}.padding()
}.statusBarHidden()
}
}

The ShareLink view creates a button with a predefined label that includes an
SF Symbol on the left. In this example, we place it at the top left corner, but
only show it when there is a picture to share (when the user has already
taken a picture with the camera). If the button is pressed, the system
opens a small sheet with icons that represent the apps with which we can
share information, and if we scroll the sheet up, options are revealed to
perform additional actions like copy and print the data. For instance, if we
have the Facebook app installed, we can post a message with our picture,
as shown below.
Figure 18-4: Share sheet
AVCaptureDeviceInput(device: AVCaptureDevice)—This
initializer creates an input for the device specified by the device
argument.
photoOutput(AVCapturePhotoOutput,
didFinishProcessingPhoto: AVCapturePhoto, error: Error?)—
This method is called on the delegate after the image is captured. The
didFinishProcessingPhoto argument is a container with information
about the image, and the error argument is used to report errors.
To control the flow of data from input to output, the framework defines
the AVCaptureSession class. From an instance of this class, we can control the
inputs and outputs and determine when the process begins and ends by
calling the following methods.
The input, output, and preview layers are connected to the capture session
by objects of the AVCaptureConnection class. The class manages the
information for the connection, including ports and data. The following are
some of the properties provided by this class.
The interface we are going to create for this example is similar to the
previous ones. We need a button to open the view that allows the user to
take a picture with the camera, and an Image view to show it on the screen.
The process to activate the camera and get the picture taken by the user is
independent of the interface, but if we want to let the user see the image
coming from the camera, we must create a preview layer and add it to a
UIKit view. As we have seen before, UIKit views are created from the UIView
class, so we need to define a representable view (see Chapter 16).
For this example, we are going to manage all the logic for the camera in a
model. The following are the basic elements we need to set up the system.
class ViewData {
var captureSession: AVCaptureSession!
var stillImage: AVCapturePhotoOutput!
var previewLayer: AVCaptureVideoPreviewLayer!
var imageOrientation: UIImage.Orientation!
}
class ApplicationData: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
@Published var path = NavigationPath()
@Published var picture: UIImage?
override init() {
cameraView = CustomPreviewLayer()
viewData = ViewData()
super.init()
Task(priority: .background) {
await receiveNotification()
}
}
func receiveNotification() async {
let center = NotificationCenter.default
let name = await UIDevice.orientationDidChangeNotification
for await _ in center.notifications(named: name, object: nil) {
if viewData.captureSession != nil {
await MainActor.run {
viewData.previewLayer.frame = cameraView.view.bounds
let videoOrientation = getCurrentOrientation()
let connection = viewData.previewLayer.connection
connection?.videoOrientation = videoOrientation
}
}
}
}

This code is only the first part of our model, we still need to add a few
methods to activate and control the camera, but it provides the set of
properties we need to store references to every element of the system and
instantiate the UIView view we need to show the preview layer. Because
these properties are required by multiple methods, we declare them in a
separate class called ViewData. When the model is initialized, we create an
instance of this class and the representable view (CustomPreviewLayer), and
then run an asynchronous method to listen to the
orientationDidChangeNotification notification to be able to adapt the size and
orientation of the preview when the device's orientation changes (see
Chapter 14, Listing 14-11).
The next step is to define a method to ask for the user's permission to
access the camera. This is done automatically when we use a
UIImagePickerController controller, but we have to do it ourselves in a custom
controller using the type methods provided by the AVCaptureDevice class. The
following is the method we must add to our model for this purpose.
We can create and add to the session all the inputs and outputs we need,
in any order, but because the AVCaptureDeviceInput() initializer throws an
error, we use it first. This initializer creates an object that manages the
input for the capture device. If the initializer is successful, we add it to the
capture session with the addInput() method and then create the output. For
this example we have decided to use the session to capture a still image, so
we use the AVCapturePhotoOutput class to create the output and add it to the
session with the addOutput() method.
After adding the inputs and outputs to the capture session, the
prepareCamera() method executes an additional method called showCamera() to
generate the preview layer and show the video coming from the camera on
the screen. In this method, we must create the layer and set its size and
orientation.
Listing 18-12: Showing the video from the camera on the screen

func showCamera() {
let width = cameraView.view.bounds.size.width
let height = cameraView.view.bounds.size.height
viewData.previewLayer = AVCaptureVideoPreviewLayer(session: viewData.captureSession)
viewData.previewLayer.videoGravity = .resizeAspectFill
viewData.previewLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
switch deviceOrientation {
case .landscapeLeft:
currentOrientation = AVCaptureVideoOrientation.landscapeRight
viewData.imageOrientation = .up
case .landscapeRight:
currentOrientation = AVCaptureVideoOrientation.landscapeLeft
viewData.imageOrientation = .down
case .portrait:
currentOrientation = AVCaptureVideoOrientation.portrait
viewData.imageOrientation = .right
case .portraitUpsideDown:
currentOrientation = AVCaptureVideoOrientation.portraitUpsideDown
viewData.imageOrientation = .left
default:
if UIDevice.current.orientation.isLandscape {
currentOrientation = AVCaptureVideoOrientation.landscapeRight
viewData.imageOrientation = .up
} else {
currentOrientation = AVCaptureVideoOrientation.portrait
viewData.imageOrientation = .right
}
break
}
return currentOrientation
}

At this point, the video is playing on the screen and the system is ready to
perform a capture. The process to capture an image is initiated by the
output object. The AVCapturePhotoOutput class we use to capture a still image
offers the following method for this purpose.
When the user presses the button to take the picture, the takePicture()
method is executed and the capturePhoto() method is called to ask the output
object to capture an image. After the image is captured, this object sends
the result to a delegate method. Notice that we declared the ApplicationData
class as the delegate object (see Listing 18-9), so we can declare the
delegate method in the model. The following is our implementation of this
method.
There is nothing new in this view, with the exception that now instead of
opening a UIImagePickerController with a standard interface, we open a view
that has to provide the buttons and custom controls required for the user
to take a picture. The following is our implementation of this view.
As illustrated in Figure 18-6, this view includes our UIView to show the video
coming from the camera and another view on top to provide two buttons,
one to cancel the process and dismiss the view, and another to take a
picture. When the view appears on the screen, we call the getAuthorization()
method to start the process. If the user presses the Take Picture button, we
call the takePicture() method to capture the image. Once the image is
processed, the view is dismissed by the delegate method, and the picture
is shown on the screen.
SwiftUI defines the VideoPlayer view to play videos. This view provides all the
controls required to play, stop, and move the video back and forth. The
view includes the following initializer.
The VideoPlayer view presents the interface for the user to control the video,
but the video is played by an object of the AVPlayer class. The class includes
the following initializer.
The AVPlayer class also includes properties and methods to control the
video programatically.
The VideoPlayer view requires an AVPlayer object to play the video, and this
object loads the video from a URL. The best way to prepare this
information is with a model. In the following model, we get the URL of a
video in the bundle called videotrees.mp4, and create an AVPlayer object
with this value.
init() {
let bundle = Bundle.main
if let videoURL = bundle.url(forResource: "videotrees", withExtension: "mp4") {
player = AVPlayer(url: videoURL)
}
}
}

The VideoPlayer view and the AVPlayer class are defined in the AVKit
framework. In this example, we import the framework and store the
AVPlayer object in a @Published property to make it available for the view. In
the view, we need to check this property and show the VideoPlayer view if
there is a video to play.
In the previous example, the video doesn't play until the user presses the
play button. But we can implement the AVPlayer properties and methods to
control the video programatically. For instance, the following example
starts the video as soon as the view is loaded.
The initializer of the VideoPlayer view can also include an argument that
takes a closure to add a layer of views over the video. For instance, in the
following example we implement this initializer to add a label with the
video's title at the top.
The views returned by the closure are placed over the video but below the
controls, so they cannot take input from the user, but we can use them to
provide additional information, as in this case. The result is shown below.

Custom Video Player

In addition to all the code necessary for the VideoPlayer view to work, the
AVFoundation framework also offers classes to create each component of
the structure required to play media. There is a class in charge of the asset
(video or audio), a class in charge of providing the media to the player, a
class in charge of playing the media, and a class in charge of displaying the
media on the screen. Figure 18-9 illustrates this structure.
An asset contains static information and cannot manage its state when it is
being played. To control the asset, the framework defines the AVPlayerItem
class. With this class, we can reference an asset and manage the timeline.
The class includes multiple initializers. The following is the most frequently
used.
AVPlayerItem(asset: AVAsset)—This initializer creates an
AVPlayerItem object to represent the asset defined by the asset
argument.
The AVPlayerItem class also includes properties and methods to control the
status of the asset. The following are the most frequently used.
All these classes define the system we need to play media, but we also
need a way to control time. Because the precision of floating-point values
is not suitable for media playback, the framework implements, among
other things, the CMTime structure from an old framework called Core
Media. The structure contains multiple values to represent time as a
fraction. The most important are value and timescale, which represent the
numerator and denominator, respectively. For example, if we want to
create a CMTime structure to represent 0.5 seconds, we may declare 1 as
the numerator and 2 as the denominator (1 divided by 2 is equal to 0.5).
The class includes initializers and type properties to create these values.
The following are the most frequently used.
Now that we have the representable view, the next step is to build the
video player and then call the play() method from the observeValue() method
to start playing the video as soon as it is ready.
func setObserver() {
playerItem.addObserver(self, forKeyPath: "status", options: [], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change:
[NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if playerItem.status == .readyToPlay {
playerItem.removeObserver(self, forKeyPath: "status")
player.play()
}
}
}
class ApplicationData: ObservableObject {
var customVideoView: CustomPlayerView!
var viewData: ViewData
init() {
customVideoView = CustomPlayerView()
viewData = ViewData()
Task(priority: .background) {
await receiveNotification()
}
}
func receiveNotification() async {
let center = NotificationCenter.default
let name = await UIDevice.orientationDidChangeNotification
for await _ in center.notifications(named: name, object: nil) {
if viewData.playerItem != nil {
await MainActor.run {
viewData.playerLayer.frame = customVideoView.view.bounds
}
}
}
}
}

Because the observer methods are defined in the NSObject class, they can
only be implemented in a class that inherits from NSObject. For this reason,
we set up and respond to the observer in the ViewData class. First, we define
the three properties we need to store the player item, the player, and the
layer, then we define a method that sets an observer for the status property
of the AVPlayerItem object, and finally we respond to that observer with the
observeValue() method. If the current status is readyToPlay, we remove the
observer and play the video.
To set up the video player, we load the video from the bundle and create
the player structure as soon as the model is initialized. The player is
associated with an AVPlayerLayer layer and the layer is added as a sublayer
of the UIView view. The size of this layer is set as the size of the screen or
the view, depending on what values are available at the moment.
In the interface, we only need to present the representable view. The video
is shown full screen, it adapts to the screen orientation, and it is played as
soon as the view is loaded.
The previous example plays the video, but it does not provide any tools for
the user to control it. The AVPlayer class includes methods to play, pause,
and check the state of the media, but we are responsible for creating the
interface. For the following example, we are going to create an interface
that provides a button and a progress bar to let the user play the video,
pause it, and see the progression.
Figure 18-10: Controls for a custom video player
How we control the process and respond to the interface depends on the
requirements of our application. For this example, we have decided to
define two states, one to indicate if the video is playing or not, and another
to determine the size of the progress bar. The following are the changes we
must introduce to our model to allow the user to play and pause the video
and to update the progress bar.
init() {
customVideoView = CustomPlayerView()
viewData = ViewData()
The toolbar includes a button and a Rectangle view that represents the
progress bar. The label for the button depends on the value of the playing
property. If the video is playing, we show the text "Pause" and when it is
paused, we show the text "Play". To calculate the size of the Rectangle view
that represents the progress bar, we embed the view in a GeometryReader
and then multiply its width by the value of the progress property. Because
this property contains a value between 0.0 and 1.0, the operation returns
the value we need to set the width of the bar and show the progression on
the screen.
Do It Yourself: Update the model with the code in Listing 18-25 and
the ContentView view with the code in Listing 18-26. Run the
application. You should see the video player illustrated in Figure 18-
10.
func setObserver() {
playerItem1.addObserver(self, forKeyPath: "status", options: [], context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change:
[NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if playerItem1.status == .readyToPlay {
playerItem1.removeObserver(self, forKeyPath: "status")
player.play()
}
}
}
class ApplicationData: ObservableObject {
@Published var playing: Bool = false
@Published var progress: CGFloat = 0
var customVideoView: CustomPlayerView!
var viewData: ViewData
init() {
customVideoView = CustomPlayerView()
viewData = ViewData()
Task(priority: .background) {
await receiveNotification()
}
}
func receiveNotification() async {
let center = NotificationCenter.default
let name = await UIDevice.orientationDidChangeNotification
for await _ in center.notifications(named: name, object: nil) {
if viewData.player != nil {
await MainActor.run {
viewData.playerLayer.frame = customVideoView.view.bounds
}
}
}
}
}

This example assumes that we are using the simple ContentView view
defined in Listing 18-24. The code loads two videos, videotrees.mp4 and
videobeaches.mp4, and then creates two AVURLAsset objects and two
AVPlayerItemobjects to represent them. The AVQueuePlayer object is define
next to play both videos in sequence. Notice that because the interface we
are using for this example does not include a button to play the videos, we
add an observer to the first video and call the play() method as soon as it is
ready.
The ColorPicker view shows a button that opens an interface for the user to
select a color. Once the user performs the selection, the color is
automatically assigned to the state property. This means that the user can
change the selection as many times as he or she wants, but only the last
selected color is preserved by the property.
The panel includes a + button at the bottom to add more platforms and
configurations. For instance, to create applications for Mac computers, we
have three options available: Mac, Mac Catalyst, and Designed for iPad.
With the Mac option we can create a Mac application with SwiftUI and
have access to all the macOS exclusive features. This is the option set by
default and the one recommended for new applications. The Mac Catalyst
option can adapt an iPad app to the Mac, and therefore it is recommended
to convert our existent iPad apps to Mac apps. And the Designed for iPad
option allows us to run our iPad apps on Macs with no modifications (not
recommended).
As mentioned in Chapter 5, Xcode includes buttons on the toolbar to select
the app's scheme and the destination, which includes simulators, real
devices, and options to execute the app on the Mac. To run the app on our
computer, the option is called My Mac. (The My Mac Rosetta option is used
to run applications that were developed to work on Intel processors.)
Xcode also allows us to provide images for each platform. The options are
available from the Attributes Inspector panel when we select a set in the
Asset Catalog. For instance, if we select an image set and activate the Mac
option from this panel (Figure 19-4, number 1), the set includes
placeholders to add images that will only be available when the app is
running on the Mac.

Conditional Code

Although the system can automatically build the application for each
platform, it is our responsibility to discriminate platform-specific code. One
alternative is to use conditional compilation. These conditionals are
checked before the code is compiled and, therefore, we can use them to
select the code we want to implement according to the target platform.
Conditional compilation in Swift is done using the #if, #else, and #endif
keywords. The #if and #else keywords work like the Swift conditionals if else,
but because the statements are not delimited by a block, the #endif
keyword is required to signal the end of the code.
There are several parameters we can use to set the condition, but to detect
whether the application is being compiled for iOS or macOS, we can use
the os() instruction and the values iOS and macOS, as in the following
example.
Mac applications include a menu bar that is displayed at the top of the
screen for easy access to the application's key features.
The options included with the menu are predefined by the system and
provide basic functionality to the app. To introduce changes and add
custom functionality, SwiftUI includes the following modifier.
This modifier is applied to the Scene (the WindowGroup structure in the App
structure) and returns a new Scene with the menu bar configured by the
closure. To define the menus and the options from this closure, SwiftUI
includes the CommandMenu and CommandGroup structures. The CommandMenu
structure is used to create new menus. The following is the structure's
initializer.
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
#if os(macOS)
.commands {
CommandMenu("Options") {
Button("Option 1") {
print("This is the option 1")
}
Button("Option 2") {
print("This is the option 2")
}
}
}
#endif
}
}

The menu options are generated with Button views. In this example, we
include two: Option 1 and Option 2. The new menu is added between the
View and Window menus, as shown below.
IMPORTANT: You can add all the options you want to a menu. If you
need to separate the options in groups, use a Divider view between
Button views to draw a line.
In addition to our own menus, we can also add options to the standard
menus or replace the options provided by the system with the
CommandGroup structure. This structure defines three initializers that we can
use to insert a new option before or after a system option, and also replace
an existing one. For instance, the system includes an option in the File
menu called New Window. This option is represented by the newItem
property defined by the CommandGroupPlacement structure. The following
example shows how to use this property to add an option to the File menu
after the New Window option.
Listing 19-8: Defining a property in the model to store the index of the
selected option

import SwiftUI
The @Published property in this model stores an integer value that we will
use to identify the options in the picker with a tag() modifier. When an
option is selected, the value in the tag() modifier is assigned to the
@Published property in the model, so views know which option is currently
selected.
@main
struct TestApp: App {
@StateObject var appData = ApplicationData()
The process to receive the data is similar to the one we used before to
process drag and drop operations (see Drag and Drop Gesture in Chapter
12). We must apply the modifier, specify a data type that conforms to the
Transferable protocol to determine the type of data we want to accept, and
then use the value received by the closure to process the data. But
because the devices send the images in JPEG format, we cannot implement
standard types like Image or Data. Instead, we must create a custom
structure that conforms to the Transferable protocol and it is configured to
import data with a jpeg content type. For our example, we call it
ImageRepresentation.
#if os(macOS)
struct ImageRepresentation: Transferable {
let image: NSImage
The macOS system does not implement the UIImage class to store images,
but the NSImage class. Therefore, the ImageRepresentation structure includes a
property to store a value of this type with the image received from the
device. When a value is received, the importableFromServices() modifier reads
this property, creates an Image view with the value, and assigns it to a @State
property to show the image on the screen.
Now we can disable the option in the menu when this property is empty.
Listing 19-14: Defining a TextField view to enable and disable the option

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
When the user inserts a value in the text field, the option is enabled, but it
is immediately disabled when the field is empty.

Do It Yourself: Update the ApplicationData class with the code in Listing
19-12, the commands() modifier in the App structure with the code in
Listing 19-13, and the ContentView view with the code in Listing 19-14.
Run the application on the Mac. Open the File menu. The option
should be disabled. Insert a text in the text field and open the menu
again. Now the option should be enabled.
In the last example, we disable the option if a condition is not met, but
often options are enabled or disabled depending on which element on the
interface is focused. For example, we may have two TextField views, but the
action can only be performed when the user is working on one of them
(the text field is focused). In Chapter 6, we have learned how to handle
focus changes in a view, but to pass the focus state from one view to
another, or as in this case, from a view to the menu bar, we need to
implement a structure called FocusedValues. This structure is a collection of
values managed by the system that contains the state of the focused view.
Each state is identified by a structure that conforms to the FocusedValueKey
protocol, which only requirement is a typealias with the name Value and the
data type of the value managed by the view we want to monitor. Once we
have this structure, we need to add a property of this type to the
FocusedValues structure with an extension, as in the following example.
To observe the value from the focused view, SwiftUI includes the
@FocusedValue property wrapper. This property wrapper is created from the
FocusedValue structure, which includes the following initializer.
When the second TextField view is focused, the focusedValue() modifier assigns
the value of the inputAddress property to the address property of the
FocusedValues structure, so the value is available for other views. Now we can
observe this value from the App structure with the @FocusedValue property
wrapper.
@main
struct TestApp: App {
@StateObject var appData = ApplicationData()
@FocusedValue(\.address) var addressValue: String?
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(appData)
}
#if os(macOS)
.commands {
CommandGroup(after: .newItem, addition: {
Button("Option 1") {
print("This is option 1")
}.disabled(addressValue == nil)
})
}
#endif
}
}

The view defines a property called addressValue, which contains the value of
the address property of the FocusedValues structure. If the TextField view that
allows the user to insert an address is focused (the user is typing on it), this
property contains the value inserted by the user, otherwise, the property
returns nil, so we can enable or disable the menu option accordingly.
Instead of Navigation Bars, Mac applications use a toolbar at the top of the
window where we can add all the items we need. The toolbar is added at
the top of the right column in a two column design created with a
NavigationSplitView view. This means that we can use the navigationTitle()
modifier to show a title, but SwiftUI also allows us to add a subtitle for
macOS applications with the following modifier.
Like the Navigation Bar, the toolbar in a Mac application can also contain
buttons. The buttons are incorporated with the same toolbar() modifier
implemented before. For instance, the following application defines a
NavigationSplitView view with two views, one to create the content for the left
column, and another for the right.
The MenuView view defines the left column and only needs some content for
this example. On the other hand, the DetailView view is the one that shows
the toolbar and where we should define the toolbar items, as in the
following example.
This view defines the title and subtitle and adds a button with an SF
Symbol to the bar. We use the automatic value to place it, which positions
the button on the right, but we could also have applied the principal value,
which positions the buttons at the center.
SwiftUI defines a few modifiers that are exclusive for Mac applications. The
following are the most frequently used.
The help() modifier is simple. All it does is to show a label when the mouse
moves over the view and remains in that position for a few seconds. Its
purpose is to help the user discover all the tools and learn how to use
them. For instance, we can apply it to show a message for the Add Book
button added to the toolbar in the previous example.
If we position the mouse over the button, after a few seconds, the system
shows a small label with the text "Press this button to add a book".
The collapsible() modifier and the inset() type method apply to List views. The
collapsible() modifier is used to collapse sections in a sidebar list, while the
inset() method returns a style for the list that highlights rows to make them
easy to identify.
To test these two features, we are going to create a small app that shows a
list of items selected from a category. The following is the model with some
testing values.
init() {
let items1 = ConsumableItems(name: "Fruits", items: ["Apples", "Avocado", "Bananas",
"Blueberries", "Grapes", "Lemons", "Oranges", "Peaches"])
let items2 = ConsumableItems(name: "Dairy", items: ["Milk", "Butter", "Cheese", "Yogurt",
"Cream", "Ice Cream"])
let items3 = ConsumableItems(name: "Juice", items: ["Apple", "Orange", "Grape"])
listOfItems = [
ConsumableSections(name: "Foods", sectionItems: [items1, items2]),
ConsumableSections(name: "Beverages", sectionItems: [items3])
]
}
}

The model defines two structures, one to store an array of items, and
another to define the sections. In the initializer, we define the @Published
property we need to store the values and initialize it with two sections and
three lists of values.
Now, we need to organize the interface. The NavigationSplitView view needs a
view to list the categories on the left and another to show the list of items
on the right.
This example applies the collapsible() modifier to the Section view, so the user
can collapse or expand each section on the list. Notice that for the
collapsible() modifier to work, the style of the list must be sidebar.
The list is created from the ConsumableSections structures returned by the
listOfItems property, and each section is created from the values in the
structure's sectionItems property. When the user selects a row, the
ConsumableItems structure is passed to the DetailView view and the values are
displayed on the list, as shown next.
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
#if os(macOS)
Window("My Window", id: "mywindow") {
AuxiliaryView()
}
#endif
}
}

Just like we did with other Environment properties, such as dismiss and
dismissSearch, we must define an @Environment property and then call it to
perform the action. The following is the ContentView view we need to open
the auxiliary view created in the previous example.
The interface includes a Button view to open the auxiliary window. When
the button is pressed, we perform the action with the id argument to open
the window identified with the string mywindow. The result is shown
below.
These modifiers are applied to the Scene in the App structure. In the
following example, we assign the auxiliary window a size of 200 by 200
points, position it at the top left corner of the screen, and remove the
menu option generated by the system to open it.
@main
struct TestApp: App {
@AppStorage("totalItems") var totalItems: Int = 0
There are two different styles for the control. We can open a menu with
options created by Button views, or a view with a custom interface. To select
the style, the framework includes the following modifier.
By default, the style is set to automatic, which means the control will open a
menu. To define the options for the menu, we include Button and Divider
views, as shown next.
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
#if os(macOS)
MenuBarExtra("My Control", systemImage: "phone") {
Button("Option 1") {
print("Option 1")
}
Button("Option 2") {
print("Option 2")
}
Divider()
Button("Quit") {
NSApplication.shared.terminate(nil)
}
}
#endif
}
}


Scene Storage

When we open a new instance of our app (a new window), the WindowGroup
structure creates a new Scene. Each Scene implements the same views and
work with the same model. This means that all the Scenes will present the
same values and share the same initial state. But this is not always
appropriate. Users often expect the window to be in the state it was before
the app was closed or control different information on each window. To
store information pertaining to a Scene, SwiftUI includes the @SceneStorage
property wrapper. This is like the @AppStorage property wrapper but instead
of storing values for the app, it stores values for the Scene.
In the following example, we create an application that allows the user to
select a picture. The index of the selected picture is permanently stored in
a @SceneStorage property, so the value is restored when the app is launched
again. First, we need to define a model with the list of pictures available.
init() {
picturesList = ["bagels", "brownies", "butter", "cheese", "coffee", "cookies", "donuts", "granola",
"juice", "lemonade", "lettuce", "milk", "oatmeal", "potato", "tomato", "yogurt"]
}
}

The interface must include a Picker view to select the picture and an Image
view to show the selected picture on the screen.
IMPORTANT: There are multiple ways to restore the state of the app
and the windows. For more information and to learn about
advanced options, visit our website and follow the links for this
chapter.
CHAPTER 20 - APP STORE
20.1 Publishing

At the beginning of this book, we talked about Apple’s strict control over
the applications users can access. Applications for Mac computers can be
sold separately, but mobile applications can only be sold in the App Store.
The tools to submit our application to the App Store are provided by
Xcode, but there are a series of requirements we need to satisfy for our
app to be published and become available to users.
Developing and testing can be done with a free account, but publishing our
app requires a membership to the Apple Developer Program. The option to
enroll in this program is available on the developer.apple.com website. We
must click on the Discover/Program options at the top of the screen, press
the Enroll button, and follow the instructions to register an account for an
Individual or an organization. At the time of writing, the membership costs
USD 99 per year.
Certificates, Provisioning Profiles, and Identifiers

Apple wants to make sure that only authorized apps are running on its
devices, so it requests developers to add a cryptographic signature to each
application. There are three values that are necessary to authorize the app:
certificates, provisioning profiles, and identifiers. Basically, a certificate
identifies the developer that publishes the application, the provisioning
profile identifies the device that is allowed to run the application, and an
identifier, called App ID, identifies the application. These values are packed
along with the application’s files and therefore Apple always knows who
developed the app, who is authorized to run it, and in which devices.
Xcode automatically generates these values for us, so we do not have to
worry about them, but Apple offers a control panel in our developer
account in case we need to do it manually (the option is not available for
free members). Figure 20-1 shows the menu we see after we go to
developer.apple.com, click on Account, and select the option Certificates,
IDs & Profiles.
Before submitting the app to the App Store, we need to provide the
resources Apple needs to make our app available to the public. An
important resource we must include in our project are the app's icons.
Icons are the little images that the user taps or clicks to launch the app. By
default, the Asset Catalog includes a set called AppIcon to manage the
icons for the application. The set includes placeholders for every icon we
need and for every scale and device available.
Icons may be created with any image editing software available on the
market. A file must be created for every size required. For example, the
first two placeholders require images of 20 points, which means that we
need to create an image of a size of 40x40 pixels for the 2x scale and an
image of a size of 60x60 pixels for the 3x scale. After all the images are
created, we must drag them to the corresponding squares, as we do with
any other image. Figure 20-2, below, shows what the AppIcon set may look
like once all the icons are provided (sample files are available on our
website).
Figure 20-2: AppIcon set with the icons for iPhones and iPads

Launch Screen

The launch screen is the first screen the user sees when the app is
launched. No matter the size, applications always take a few seconds to
load. The launch screen is required to give the user the impression that the
app is responsive.
We can define two aspects of the launch screen: the background color and
an image. The values are specified from the Info panel in the app's settings.
The option is called Launch Screen. When we press the + button on this
key, Xcode shows a list of options to choose from.
There are two options available to define the content and four to configure
the screen. The Background color option specifies the name of the Color
Set in the Asset Catalog we want to use to define the screen's background
color, and the Image Name option specifies the name of the Image Set that
contains the image we want to display. For configuration, we have the
Image respects safe area insets to determine the behavior of the image
regarding the safe area, and the Show Navigation bar, Show Tab bar, and
Show Toolbar options to determine whether these bars are going to be
shown while the app is launched.
For instance, below is what we see if we include a background color and an
image. This requires the Asset Catalog to include an Image Set called
launchLogo and a Color Set called launchColor.
Figure 20-4: Launch Screen configuration

App Store Connect

From this panel, we can insert our financial information (Agreements, Tax,
and Banking), publish our apps (My Apps), and see how the business is
going (Sales and Trends). The first step is to create a record for the app we
want to publish from the My Apps option. When we click on this icon, a
new window shows the list of our apps and a + button at the top to add
more.

To add a new app, we must select the New App option and insert the app’s
information. The first window asks for the platform we have developed the
app for (in our case, iOS and macOS), the application’s name, the primary
language, the bundle ID, and a custom ID (SKU) that can help us identify
the app later. The name and language are values we already have, and the
SKU is a custom string, but the Bundle ID is a value generate by Xcode.
Xcode creates a Bundle ID and submits it to Apple servers when we enable
services from the capabilities panel. If our app does not use any of these
services, we can register a new Bundle ID from developer.apple.com.
After these values are inserted, we can press the Create button and
complete the rest of the information. This includes the app’s description,
screenshots, and personal information. We also must select the option
Pricing and Availability on the left panel to set the price and where the
application will be available. Once all the information is provided, we can
finally press the Save button and go back to Xcode to upload the files.
Submitting the Application

After we click on the Archive option (Figure 20-9, number 2), Xcode
compiles the application and creates the archive. The next window shows
the archive and offers buttons to validate and submit the app.
IMPORTANT: The app’s version and the number of the build (archive)
are determined from the app’s settings (by default, both values are
set to 1.0). If we want to specify a different version, we must declare
the numbers separated by one or two periods (e.g., 1.0 or 1.2.5). The
values represent different revisions of our app, with the order of
relevance from left to right. The values are arbitrary, but we are
required to change them every time an update is published to the
App Store to reflect how big the update is.
All the options are recommended. The first one asks Xcode to include code
that improves the app’s performance, and the second one uploads the
necessary information for Apple to be able to report errors and perform
diagnostics.
The next window allows us to select how we want to sign the app. With
automatic signing, we let Xcode take care of everything for us
(recommended).
The last window displays a summary and provides a button to initiate the
validation process. Once this process is over, if no errors are found, we can
finally submit our app to Apple servers by pressing the Distribute App
button (Figure 20-10, number 2).
As we already mentioned, we may submit multiple archives to the server
(builds). For this reason, we must go back to the App Store Connect
website, open the description of our application, and select the archive we
just uploaded (it may take a few minutes to be available). Figure 20-13,
below, shows the option with the archive uploaded for an app in its version
3.3.

After the archive is selected, we can press the Save button to save the
app’s description. If all the required information was provided, we can
finally press the Submit for Review button at the top of the page to submit
the application. The system asks a few questions and then the application
is sent to Apple for review (the message Waiting for Review is shown
below the app’s title).
The process takes a few days to complete. If the app is approved, Apple
sends us an email to let us know that the app has become available in the
App Store.
Find more books at
www.formasterminds.com
J.D Gauchat
www.jdgauchat.com