SwiftUI 4 Page
SwiftUI 4 Page
for Masterminds Companies, services, or product names used in this book are for
identification purposes only. All trademarks and registered trademarks are
How to take advantage of Swift and SwiftUI the property of their respective owners.
to create insanely great apps for
iPhones, iPads, and Macs Apple™, iPhone™, iPad™, Mac™, among others mentioned in this work, are
trademarks of Apple Inc.
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
Conventions
This book explores basic and advanced topics required to develop
professional applications. Depending on your current level of knowledge
and experience, you may find some of these topics easy or difficult to
learn. To help you navigate the book, we have identified each section with
a label. The following is a description of what these labels represent.

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
CHAPTER 1 - APP DEVELOPMENT
Every single topic presented in this book is explained through examples
that you can try yourself. We recommend that you open Xcode and try the
examples as you learn, but you can download the codes and projects from
our website to save time (www.formasterminds.com).
The examples in this book only apply the technologies you already know,
so they don't always follow best practices. There are several programming
patterns and best practices you can follow. What applies to you depends
on the characteristics of your application and what you want to achieve
with it. We recommend you explore all the possibilities presented in this
book but also experiment and try your own.
Apple has provided tools for developers to create apps for its devices since Apple requires developers to develop the applications with software
the introduction of the first iPhone in 2007. But the Apple ecosystem has provided by the company, and this software only works on Apple
grown significantly since then. Developers now need to consider that users computers. For this reason, the options are limited, but the good news is
own many devices, including iPhones, iPads, Mac computers, the Apple that the tools and accounts we need are provided by the company for free.
Watch, and Apple TV. Having all these devices available with such
distinctive features is great for users, but difficult for developers. Creating Mac Computer—This in theory could be any Mac computer, but the
apps that work across multiple platforms is demanding and involves a development software always requires the latest operating system
steep learning curve. This resulted in many applications being distributed (currently macOS Ventura), so in practice we need a relatively new
exclusively for one system or another. Apple engineers quickly realized that computer with a recommended 16GB of memory.
the tools were not ready to meet the demands of modern developers, and Xcode—This is the software provided by Apple for development. The
in June 2019, they released SwiftUI. latest version is number 14. It’s free and the package comes with
SwiftUI is an abstraction layer that sits on top of previous tools to simplify everything we need to create our apps, including an editor, the SDK
the construction of the user interface and revamp the way developers (Software Development Kit), and a simulator to test the applications.
create applications for Apple devices. With SwiftUI, we can easily develop
Apple Developer Account—This is a basic account we can get for
apps that share data and work seamlessly on all devices and any screen
free. From this account, we can manage our membership, create
size.
certificates, app identifiers and other information we need to test and
publish our apps.
Apple Membership—This is the membership required to publish
our apps in the App Store. As of this writing, the cost of this
membership is $99 US dollars per year.
Mobile Device—This could be any of the devices available in the
market that support the current versions of Apple’s mobile operating
systems (currently iOS 16, tvOS 16, watchOS 9, iPadOS 16, and macOS
Ventura). Testing our applications on a real device is not required but
highly recommended.
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.

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.
Programming Languages Frameworks and APIs
 
Several years ago, Apple adopted and implemented a language called Programming languages by themselves cannot do much. They provide all
Objective-C to allow developers to create applications for its devices. Due the elements to interact with the system but are basic tools for data
to the technical level required to work with this language, the spectacular management. Because of the complexity of the information required to
success of Apple's mobile devices did not impress developers the same control sophisticated technologies and access every part of a system, it
way as consumers. The demand for more and better applications was could take years to develop an application from scratch working with just
growing fast, but the complicated nature of the system did not appeal to the instructions of a programming language. Doing simple things like
most developers who were used to working with more traditional tools. To printing graphics on the screen or storing data in files would become a
solve this problem, in 2014 the company introduced a new programming nightmare if programmers had to depend on the tools provided by
language called Swift. Swift presents a simpler syntax that developers find programming languages alone. For this reason, these languages are always
familiar, while at the same time preserves that low-level nature necessary accompanied by pre-programmed routines grouped in libraries and
to take advantage of every aspect of Apple's devices. Swift was designed to frameworks that through a simple interface called API (Application
replace Objective-C and, therefore, it is the language recommended to new programming interface) allow programmers to incorporate to their apps
developers. amazing functionality with just a few lines of code. Apple provides all this
functionality, including frameworks and their APIs, in a set of tools called
SDK (Software Development Kit) that comes with Xcode.
Frameworks and APIs are fundamental to app development. As developers,
we must learn and apply these tools if we want to create useful
applications and, therefore, they will become the main subject of study in
the following chapters.
Compiler

Playground

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 Playground presents a simple interface with a toolbar at the top and four
View (with the same code required to create the user interface for an areas: the Navigator Area where we can see the resources included in our
application). 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
Figure 2-2: Playground templates 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. A computer program is just text written with a specific syntax. Each line of
The button in the upper left corner removes the Navigator Area (number text represents an instruction. Sometimes a single line includes several
1), the one in the upper right corner controls a panel called Utilities Area instructions, and therefore each line is usually called statement. Every
with information about the selected resource, and the button in the lower
statement is an order, or a group of orders, required for the computer to
right corner opens or removes the Console.
As illustrated in Figure 2-3, the Editor Area includes a button at the bottom perform a task. In the code of Listing 2-1, the first statement uses the
of the panel to run and stop the code (Play Button). There is also a play instruction import to include in our code the pre-programmed codes from
button on the left side of the Editor Area that we can press if we want to the UIKit framework, and the second statement uses the instruction var to
execute parts of the code instead (number 4). When this button is pressed, store the text "Hello, playground" in memory.
the code is executed up to the line in which the button is located. If we press the Play Button to execute the code, we see the result inside
Playground can run the code automatically or wait until we press the Play the Results Side Bar. (In this case, the bar shows the text stored in memory
button. By default, the mode is set to Automatically Run, but we can press by the var instruction.) When we move the mouse over the text on the bar,
and hold the Play button to access a menu that allows us to modify this
two small buttons show up, as illustrated below.
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 The button on the right is called Quick Look, and it shows a popup window
few basic lines of code to start with. Listing 2-1, below, is the code with a visual representation of the result produced by the execution of the
currently generated for the Blank template. 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.
Listing 2-1: Playground template
 Figure 2-6: Quick Look window
import UIKit
2.2 Variables
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
Variables are names representing values stored in memory. Once a variable
"Hello, playground" text is shown.
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
Figure 2-7: Result window 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.
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.
Memory 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,
The computer’s memory is like a huge honeycomb, with consecutive cells with some of its switches on representing the binary number 00011101.
that can be in two possible states: activated or deactivated. They are
electronic switches with on and off positions established by low and high Figure 2-10: Representation of one Byte in memory
energy levels.
Primitive Data Types 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
Primitive data types are units of data defined by the programming limit of 127 is because it only uses 7 bits to store the value, the first bit on
language. They are always the same size, so when we store a value of one the left is reserved to indicate the sign (positive or negative). Although
of these data types, the computer knows exactly how much memory to these limits are not restrictive, the language also provides the unsigned
use. The following are the most basic data types provided by Swift. versions of these types in case we need to store larger positive values.
Int—This data type defines integer numbers, which are numbers with UInt—This is the same as Int but for unsigned values. Because it does
no fractional component. In 64 bits systems, the size of this data type not reserve a bit for the sign, in 64-bit systems it can store values from
is 8 Bytes and therefore it can store values from 0 to 18,446,744,073,709,551,615.
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
The specific data types for UInt are UInt8, UInt16, UInt32, and UInt64. These
Although it is recommended to use the Int data type to store integers, data types work exactly like the equivalents for Int, but they are intended
some frameworks require a very specific type of integer. For this reason, to store only positive numbers.
Swift also defines the following data types. 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
Int8—This data type defines integer numbers of a size of 1 Byte (8 operations also require the use of real numbers (e.g., 3.14 or 10.543).
bits). Because of its size, it can store values from -128 to 127. Computers cannot reproduce these types of values, but they can work with
Int16—This data type defines integer numbers of a size of 2 Bytes (16 an approximation called floating-point numbers. The following are the
bits). Because of its size, it can store values from -32,768 to 32,767. most frequently used floating-point data types defined in the Swift
Int32—This data type defines integer numbers of a size of 4 Bytes (32 language.
bits). Because of its size, it can store values from -2,147,483,648 to
2,147,483,647. Float—This data type defines 32 bits floating-point numbers with a
precision of 6 digits.
Int64—This data type defines integer numbers of a size of 8 Bytes (64
bits). Because of its size, it can store values from Double—This data type defines 64 bits floating-point numbers with a
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. precision of at least 15 digits.
If we calculate the size of each type presented so far and determine the Floating-point types can handle large numbers using scientific notation, but
possible combinations of bits, we will discover that the maximum values because of their precision, it is recommended to declare a variable of type
Doublewhen performing calculations and use Float for minor tasks, such as Declaration and Initialization
storing coordinates to position graphics on the screen. 
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 Variables are called variables because their values are not constant. We can
variable we must always store a value in it to clear the space. This action is change them any time we want. To store a new value in the space of
called Initialization. memory reserved for a variable, we must implement the same syntax used
for initialization.
Listing 2-3: Initializing variables
Listing 2-5: Assigning a new value to a variable


var mynumber: Int
var mynumber: Int = 5
mynumber = 5
mynumber = 87
 
In the new example of Listing 2-3, we first declare the variable as we did The process of storing a value in a variable is called assignment. In these
before, and then initialize it with the value 5 (we store the number 5 in the terms, we can say that in the example of Listing 2-5 we "initialize the
space of memory reserved for this variable). To store the value, we use the variable mynumber with the number 5 and then assign the value 87 to it".
= (equal) symbol and the syntax name = value, where name is the name of the
The value 87 replaces the value 5 in memory. After that second statement
is executed, every time we read the mynumber variable from other
variable and value is the value we want to store (once the variable was
statements in the code it will return the value 87 (unless another value is
declared, we do not have to use the var instruction or specify its type
assigned to the variable later).
anymore).
Most of the time, we know what the variable’s initial value will be right
IMPORTANT: Once a variable is declared, the values stored in that
away. In cases like this, Swift allows us to declare and initialize the variable
variable must be of the same data type. If we declare a variable of
in just one line of code.
type Int, we cannot store floating-point values in it later (e.g.,
Listing 2-4: Declaring and initializing variables in the same statement 14.129).

var mynumber: Int = 5
Of course, we can create all the variables we want and of the data type we
 need.
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.
Arithmetic Operators
The first two statements in Listing 2-10 are easy to read. They perform

arithmetic operations over integer numbers that produce an integer value,
so the variables mynumber and anothernumber will be of type Int. A problem
Storing values in memory is what variables allow us to do, but those values
arises when we work with operations that may produce floating-point
do not have to be declared explicitly, they can also be the result of
arithmetic operations. Swift supports the operations: + (addition), - numbers. That is why in the third statement we specifically declared the
(subtraction), * (multiplication), / (division) and % (remainder). values as floating-point numbers by adding the fractional part (.0). This
forces Swift to infer the variable’s type as Double and produce a result of
Listing 2-9: Assigning the result of an operation to a variable that type.
 When the compiler finds an operation with two or more numbers and has
var mynumber = 5 + 10 // 15 to infer the data type of the result, it converts the number of the less

comprehensive type to the most comprehensive type. For example, when
we declare an Int and a Double in the same operation (e.g., 5 + 2.0), the Int
When the system reads the statement in Listing 2-9, it adds 10 to 5 and
value is converted and processed as Double, and therefore the result will
assigns the result to mynumber (15).
also be Double.
IMPORTANT: The text added at the end of the statement in Listing 2- Listing 2-11: Inferring the data type from an operation
9 is a comment that we used to show the value produced by the 
statement. Comments are ignored by the compiler but useful for var myfraction1 = 5.0 / 2.0 // 2.5
programmers to remember vital information. They are introduced var myfraction2 = 5 / 2.0 // 2.5
var myfraction3 = 5 / 2 // 2
after the characters // (e.g., // comment) or in between the characters /*

*/ (e.g., /* comment */). You can write the characters yourself or use
Xcode's shortcut by selecting the lines of code you want to turn into
This example declares and initializes three variables. In the first statement
a comment and press the keys Command and /.
both numbers were declared as floating-point values, so the compiler
Of course, we can perform not only addition but any operation we want. 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
Listing 2-10: Performing operations in variables of different type 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
var mynumber = 2 * 25 // 50
var anothernumber = 8 - 40 * 2 // -72
statement there is no clear floating-point value. Both numbers were
var myfraction = 5.0 / 2.0 // 2.5 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 var mynumber = 5
var total = mynumber + 10 // 15
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 This example declares the mynumber variable and initializes it with the value
explicitly as Float or Double. 5. In the next statement, the total variable is declared and initialized with
Dividing integer numbers is pointless most of the time, except in some the result of the addition of the current value of mynumber plus 10 (5 + 10).
circumstances when we need to know the remainder. The remainder is the In Listing 2-13, we used a new variable to store the result of the operation,
amount left over by a division between two numbers and it is calculated but when the old value is not important anymore, we can store the result
using the % symbol. back into the same variable.
Listing 2-12: Calculating the remainder Listing 2-14: Performing operations on the variable’s current value
 
var remainder1 = 11 % 3 // 2 var mynumber = 5
var remainder2 = 20 % 8 // 4 mynumber = mynumber + 10 // 15
var remainder3 = 5 % 2 // 1 

In this example, the current value of mynumber is added to 10 and the result
Each statement in Listing 2-12 calculates the remainder of dividing the first
is assigned to the same variable. After the execution of the second
number by the second number and assigns the result to the variable. For statement, the value of mynumber is 15.
instance, the first statement produces the remainder 2. The system divides Working with values previously stored in a variable allows our program to
11 by 3 and finds a quotient of 3. Then, to get the remainder, it calculates evolve and adapt to new circumstances. For instance, we could add 1 to
11 minus the multiplication of 3 times the quotient (11 – (3 * 3) = 2). the current value of a variable and store the result in the same variable to
The second statement produces a remainder of 4 and the third statement create a counter. Every time the statement is executed, the value of the
produces a remainder of 1. This last statement is particularly useful variable is incremented by one unit. Recurrent increments and decrements
because it allows us to determine whether a value is odd or even. When of the value of a variable are very important in computer programming.
we calculate the reminder of an integer divided by 2, we get a result Because of this, Swift supports two operators that were specifically
according to its parity. If the number is even, the remainder is 0, and if the designed for this purpose.
number is odd, the remainder is 1 (or -1 for negative values).
Performing arithmetic operations becomes useful when instead of += is a shorthand for variable = variable + number, where number is the
numbers we use variables. value we want to add to the variable’s current value.
-= is a shorthand for variable = variable - number, where number is the
Listing 2-13: Adding numbers to variables
value we want to subtract from the variable’s current value.
Constants
With these operators, we can easily add or subtract a value to the current
value of the variable and assign the result back to the same variable. 
Listing 2-15: Modifying the value of a variable using incremental operators As we already mentioned, the memory of a computer is a sequence of
 switches. There are millions and millions of switches, one after another,
with no clear delimitations. To be able to know where the space occupied
var mynumber = 5
mynumber += 4 // 9 by a variable starts and ends, the system uses addresses. These addresses
 are just consecutive numbers that correspond to each Byte of memory (8
bits). For example, if one Byte is at the address 000000, the next Byte will
The process generated by the code in Listing 2-15 is straightforward. After be at the address 000001, the next one at 000002, and so on. If we declare
the value 5 is assigned to the mynumber variable, the system reads the a variable of 4 Bytes, the system reserves the four consecutive Bytes and
remembers where they are so as not to overwrite them with the value of
second statement, gets the current value of the variable, adds 4 to that
another variable. The task is easy when working with primitive data types
value, and stores the result back to mynumber (9). because their sizes are always the same, but the size of variables of more
complex or custom data types depends on the values we assign to them.
IMPORTANT: Swift also offers Overflow operators (&+, &-, &*, &/ and For example, the space in memory required to store the text "Hello" is
&%). These operators are useful when we think that an operation smaller than the space required for the text "Hello World". Managing the
could produce a result that goes over the limit the data type can memory for data of inconsistent sizes takes time and consumes more
handle. For more information, visit our website and follow the links resources than working with fixed sizes. This is one of the reasons why
for this chapter. Swift includes the concept of constants.
Constants are the same as variables, but their values cannot change.
Once a constant is declared and initialized, we cannot change its value.
Therefore, constants provide a secure way to store a value and help the
system to manage memory. To declare them, we must apply the same
syntax used for a variable but replace the var keyword by the let keyword.
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. 2.3 Swift Data Types
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
Strings mytext += name // "My name is John"


With the + and += operators we can concatenate strings with strings. To
Individual characters are barely used in computer programming. Instead, concatenate strings with characters and numbers we must implement a
we usually store strings of characters. The String type was created for this procedure called String Interpolation. The variables must be included
purpose. inside the string between parentheses and prefixed by a backslash.
Listing 2-18: Declaring and initializing a String variable Listing 2-21: Including variables in strings


let mytext: String = "My name is John"
let age = 44
 let mytext = "I am \(age) years old" // "I am 44 years old"

A string is a sequence of Character values. It is declared with the String type
and the value between double quotes. These types of variables are very In this code, we read the age variable and add its current value to the string.
flexible; we may replace a string by another one of different length, The string "I am 44 years old" is then assigned to mytext. Using this tool, we
concatenate two or more, or even modify parts of it. Concatenation is a can insert any value we want inside a string, including Character and String
common operation, and it is done with the + and += operators. 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.
Listing 2-19: Concatenating strings

Listing 2-22: Performing operations inside strings
var mytext = "My name is "

mytext = mytext + "John" // "My name is John"
 let age = 44
let mytext = "I am \(age * 12) months old" // "I am 528 months old"

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 Sometimes we need to include special characters in the string, like
the string "My name is John". The += operator works in a similar way, and backslashes or quotes. Swift offers two ways to achieve this purpose. We
we can also combine them to get the string we want. can prefix the special character with another backslash, or enclose the
entire string in hash characters, as shown next.
Listing 2-20: Concatenating strings with the + and += operators

Listing 2-23: Including special characters in a string
let name = "John"

var mytext = "My name is "
Another important feature of strings is the possibility to create multiple Boolean variables are a type of variables that can only store two values:
lines of text. Again, Swift offers two alternatives. We can include the special true or false. These variables are particularly useful when we want to execute
characters \n where we want to generate a new line or we can use triple an instruction or a set of instructions only if a condition is met. To declare a
quotes (""") and the compiler will consider the original format of the text Boolean variable, we can specify the data type as Bool or let Swift infer it
and automatically insert the \n characters when required, as shown next. from the value, as in the following example.
Listing 2-24: Generating multiple lines of text Listing 2-25: Declaring a Boolean variable
 
let twolines = "This is the first line\nThis is the second line" var valid = true
let multiline = """

This is the first line
This is the second line
"""
The purpose of these variables is to simplify the process of identifying a

condition. By using a Boolean variable instead of an integer, for example,
we just need to check whether the value is equal to true or false to verify the
The twolines constant defined in this example includes the \n characters
condition. We will see some practical examples later.
between sentences, which asks the compiler to generate two lines of text.
If we press the Show Result button on the Results Side Bar, Xcode
introduces a box below the code showing the two lines of text, one on top
of the other. Something similar happens with the value of the multiline
constant, although in this case the """ characters tell the compiler that it
must add the \n characters at the end of each line for us.
Optionals mynumber = 5
mynumber = nil
 
As mentioned at the beginning of this chapter, after a variable is declared, This example declares an optional integer, assigns the value 5 to it, and
we must provide its initial value. We cannot use a variable if it was not then declares the variable as empty with the keyword nil. Although
initialized. This means that a variable has a valid value all the time. But this optionals seem to work like regular variables, they do not expose their
is not always possible. Sometimes we do not have an initial value to assign values. To read the value of an optional, we must unwrap it by adding an
to the variable during development or need to indicate the absence of a
exclamation mark at the end of the name.
value because the current one becomes invalid. For these situations, Swift
defines a modifier that turns every data type into an optional type. This Listing 2-29: Unwrapping an optional variable
means that the variable marked as optional may have a value or be empty.

To declare an optional, we add a question mark after the type’s name.
var mynumber: Int?
mynumber = 5
Listing 2-26: Declaring an optional variable of type Int var total = mynumber! * 10 // 50
 
var mynumber: Int?
 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
New values are assigned to optionals as we do with normal variables. only necessary when we need to use the value. If we just want to assign an
Listing 2-27: Assigning new values to optional variables optional to another optional, the process is as always.

Listing 2-30: Assigning an optional to another optional
var mynumber: Int?
mynumber = 5 
 var mynumber: Int?
mynumber = 5
var total = mynumber
The empty state is represented by the keyword nil. Therefore, when an 
optional variable is declared but not initialized, Swift assigns the nil
keyword to the variable to indicate the absence of a value. Thus, if later we In this example, the system infers the type of the total variable to be an
need to empty the variable, we can assign the keyword nil to it. 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
Listing 2-28: Using nil to empty an optional variable
before.

var mynumber: Int?
value to assign to it, but we know that the variable will have a value as var myname: (String, String) = ("John", "Doe")

soon as the user launches the app. For these situations, Swift includes
Implicitly Unwrapped Optionals. These are optional variables declared with
In this example, the myname variable is declared to be a tuple that contains
the exclamation mark instead of the question mark. The system treats
two String values. The values of this tuple are of the same type, but we can
these variables as optionals until we use them in a statement, as in the
use any combination of values we want.
following example.
Listing 2-33: Declaring a tuple with values of different type
Listing 2-31: Declaring Implicitly Unwrapped Optionals


var myname = ("John", "Doe", 44)
var mynumber: Int!

mynumber = 5
var total = mynumber * 10 // 50
 To be able to read the values later, an index is automatically assigned to
each of the values of the tuple. The first value will be at index 0, the second
In this code, the mynumber variable was declared as an implicitly unwrapped at index 1, and so on. Using the corresponding index and dot notation we
optional and it was later initialized with the value 5. Notice that it was not can access the value we want to read.
necessary to write the exclamation mark when reading its value anymore.
The system unwraps the mynumber variable automatically to uses its value in Listing 2-34: Reading the values of a tuple

the multiplication (this is only available for implicitly unwrapped optionals).
var myname = ("John", "Doe", 44)
var mytext = "\(myname.0) is \(myname.2) years old" // "John is 44 years old"

In Listing 2-34, we read the values of the myname tuple at index 0 and 2 to 
include them in a new string and assign the string to mytext. The same
syntax may be used to modify a value. 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
Listing 2-35: Modifying the value of a tuple some of the values are required, the rest may be ignored with an

underscore, as shown next.
var myname = ("John", "Doe", 44)
myname.0 = "George" Listing 2-38: Ignoring some of the values of a tuple
var mytext = "\(myname.0) is \(myname.2) years old" 

var myname = ("John", "Doe", 44)
var (name, _, age) = myname
var mytext = "\(name) is \(age) years old"
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
Only the variables name and age are created in this last example. (Notice the
mytext is "George is 44 years old".
underscore in the place of the second variable.) The string assigned to
Indexes are a quick way to access the values of a tuple, but they do not
mytext is "John is 44 years old".
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.
Up to this point, we have written the instructions in sequence, one after A simple but handful conditional statement available in Swift is if. With if
the other. In this programming pattern, the system executes each we can check a condition and execute a group of instructions only when
statement once. It starts with the one at the top and goes on until it the condition is true. The instructions that are going to be executed are
reaches the end of the list. The purpose of Conditionals and Loops is to declared after the condition between braces.
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 Listing 2-39: Comparing two values with if
instructions repeatedly. 
var age = 19
var message = "John is old"
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 Listing 2-41: Conditions with Boolean values
group of operators called Comparison Operators. The following is the list of 
The exclamation mark is part of a group of logical operators provided by IMPORTANT: Using && (AND) and || (OR) you can create a logical
Swift. sequence of multiple conditions. The system evaluates one condition
at a time from left to right and compares the results. If you want to
! (logical NOT) toggles the state of the condition. If the condition make sure that the expressions are evaluated in the correct order,
is true, it returns false, and vice versa. you can declare them within parentheses, as in (true && false) ||
&& (logical AND) checks two conditions and returns true if both true. The expression within the parentheses is evaluated first, and
are true. the result is then evaluated against the rest of the expression.
|| (logical OR) checks two conditions and returns true if one or
both are true. Although we can use comparison operators and logical operators in most
of the data types available, optionals are slightly different. Their values are
Logical operators work with any kind of conditions, not only Booleans. To wrapped, so we cannot compare them with other values, or check their
work with complex conditions, it is recommended to enclose the condition state as we do with Booleans. Optionals must be first compared against the
between parentheses.
nil keyword and then unwrapped before working with their values.
The if statement in Listing 2-43 compares the value of the age variable with This example introduces the process we must follow to read the value of
21 and checks the value of the smart variable. If age is less than 21 and smart an optional variable. The optional is first compared against nil. If it is
is true, then the overall condition is true, and a new string is assigned to different from nil (which means it contains a value), we unwrap the
message. If any of the individual conditions is false, then the overall condition optional inside the block of statements using an exclamation mark, assign
is false, and the block of instructions is not executed. In this case, both its value to a constant, and use the constant to perform any operation
conditions are true and therefore the "John is allowed" string is assigned to necessary.
message. 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 variable declared outside the block. The code can be simplified even more
Binding, and we can use it in an if statement, as shown next. by only declaring the name of the constant, as shown next.
Listing 2-45: Using Optional Binding to unwrap an optional variable Listing 2-47: Unwrapping an optional value of the same name
 
This code is cleaner and easy to read. The optional is unwrapped as part of In this example, Swift looks for an optional variable called myoptional,
the condition. If it is different from nil, its value is assigned to the uvalue unwraps the value, and assigns it to the constant. The result is the same as
constant and the statements in the block are executed, otherwise, the before, but the code has been simplified.
statements inside the block are ignored.
IMPORTANT: The constants and variables declared inside and
As we will see later, variables and constants declared inside a block are
outside blocks have different scopes, which means they are
only available to the code in the block. This also means that variables and
independent and their values are stored in different locations in
constants declared in different blocks are independent (their values are
stored in different locations in memory). An interesting consequence of memory, even when they share the same name. We will learn more
this is that we can declare the constant for the Optional Binding with the about the scope of variables in Chapter 3.
same name as the variable, and they will be considered by Swift to be
different. If we want to unwrap several optionals at the same time using Optional
Binding, we must declare the expressions separated by comma. This also
Listing 2-46: Using the same name to unwrap a value applies when we want to check for other conditions in the same

statement. For instance, the following example unwraps an optional and
only executes the code between braces if its value is equal to 5.
var count = 0
var myoptional: Int? = 5
if let myoptional = myoptional { Listing 2-48: Checking multiple conditions with Optional Binding
count = count + myoptional // 5
} 
 var count = 0
var myoptional: Int? = 5
if let uvalue = myoptional, uvalue == 5 {
The myoptional constant declared by the if statement is only accessible by count = count + uvalue // 5
the code inside the block and therefore is different from the myoptional }


The if statement in Listing 2-48 unwraps the optional first and, if there is a var age = 19
var message = "The customer is "
value, compares it with the number 5. The statements in the block are if age < 21 {
executed only if both conditions are true (the myoptional variable contains a message += "underage" // "The customer is underage"
} else if age > 21 {
value and the value is equal to 5). message += "allowed"
Sometimes, a group of instructions must be executed for each state of the } else {
message += "21 years old"
condition. For this purpose, Swift includes the if else statement. The }
instructions are declared in two blocks. The first block is executed when 
the condition is true, and the second block when the condition is false.
If all we need from an if statement is to assign a value to a variable
Listing 2-49: Using if else to respond to both states of the condition depending on a condition, we can use a shortcut provided by Swift called
 Ternary Operator. A ternary operator is a construction composed by the
var mynumber = 6 condition and the two values we want to return for each state separated
if mynumber % 2 == 0 { with the characters ? and :, as in the following example.
mynumber = mynumber + 2 // 8
} else {
mynumber = mynumber + 1 Listing 2-51: Implementing a ternary operator
}
 
var age = 19
var message = age < 21 ? "Underage" : "Allowed" // "Underage"
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 The first value is returned if the condition is true, and the second value is
against 0. If true, it means that the value of mynumber is even, so the first returned if the condition is false. The advantage of using a ternary operator
block is executed. If the result is different from 0, it means that the value is is that it reduces the size of the code significantly, but the result is the
odd and the condition is false, so the block corresponding to the else same as using an if else statement. In our example, the string “Underage” is
instruction is executed instead. assigned to the variable message because the value of age is less than 21.
The statements if and else may be concatenated to check as many Ternary operators can also be implemented to unwrap optionals. For
conditions as we need. In the following example, the first condition checks instance, we can check whether an optional variable contains a value and
whether age is less than 21. If not true, the second condition checks assign it to another variable or give the variable a default value if the
optional is empty.
whether age is over 21. And if not true, the final else block is executed.
This example always compares the first value of the tuple against 10
Listing 2-55: Checking multiple conditions per case but checks different matches for the second value. If a value does not
 matter, we can use an underscore to ignore it.
var age = 6
var message = "You go to " Listing 2-57: Matching only the second value of a tuple
switch age {

case 2, 3, 4:
message += "Day Care" var message = ""
case 5, 6, 7, 8, 9, 10, 11: var ages = (10, 30)
message += "Elementary School" // "You go to Elementary School"
case 12, 13, 14, 15, 16, 17: switch ages {
message += "High School" case (_, 20):
case 18, 19, 20, 21: message = "Too close"
message += "College" case (_, 30):
default: message = "The right age" // "The right age"
message += "Work" case (_, 40):
} message = "Too far"
 default:
message = "Way too far"
}
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 An alternative offered by the switch statement to create complex
code checks the second value of a tuple to determine the difference in age. matching patterns is to capture a value in a constant to be able to access it
from the instructions of the case.
Listing 2-56: Matching a tuple in a switch statement
 Listing 2-58: Capturing values with constants
var message = "" 
var ages = (10, 30)
var message = ""
var ages = (10, 20)
switch ages {
case (10, 20):
switch ages {
message = "Too close"
case (let x, 20):
case (10, 30):
message = "Too close to \(x)" // "Too close to 10"
message = "The right age" // "The right age"
case (_, 30):
case (10, 40):
message = "The right age"
message = "Too far"
case (let x, 40):
default:
message = "Too far to \(x)"
message = "Way too far"
default:
}
message = "Way too far"

}

While and Repeat While
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
The conditionals studied so far execute the statements only once.
example, we just add the value to a string.)
Sometimes the program requires executing a block of instructions several
There is an even more complex matching pattern that involves the use
times until a condition is satisfied. An alternative offered by Swift to create
of a clause called where. This clause allows us to check additional conditions.
these loops is the while statement (and its sibling repeat while).
In the following example, we capture the values of the tuple with another
The while statement checks a condition and executes the statements in the
tuple and compare them against each other.
block while the condition is true. The following example initializes a
Listing 2-59: Comparing values with where variable with the value 0 and then checks its value in a while statement. If
 the value of the variable is less than 5, the statements inside the block are
var message = "" executed. After this, the condition is checked again. The loop keeps running
var ages = (10, 20) until the condition is false (the value of the counter variable is equal or
switch ages { greater than 5).
case let (x, y) where x > y:
message = "Too young"
case let (x, y) where x == y: Listing 2-60: Using while to create a loop
message = "The same age" 
case let (x, y) where x < y:
message = "Too old" // "Too old" var counter = 0
default: while counter < 5 {
message = "Not found" counter += 1
} }
 
Every time the switch statement tries to match a case in this example, it If the first time the condition is checked it returns false, the statements in
creates a tuple and assigns the values of ages to it. The where clause the block are never executed. If we want to execute the statements at least
compares the values and when the condition is true, it executes the once, we must use repeat while.
statements inside the case.
Listing 2-61: Using repeat while to create a loop

var counter = 10
repeat {
counter += 1
} while counter < 5

For In
In this case, the initial value of the counter variable is declared as 10. This is

greater than 5, but since we are using the repeat while instruction, the
statements in the block are executed before the condition is checked, so
The purpose of the for in loop is to iterate over collections of values, like the
the final value of counter will be 11. (Its value is incremented once and then
strings of characters studied before. During the execution of a for in loop,
the condition returns false, ending the 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. Listing 2-64: Adding a condition to a loop
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, var mytext = "Hello"
var counter = 0
nothing is added by the first statement in the block. Then, the second
for letter in mytext where letter != "l" {
statement adds the value of letter to the current value of message and the
counter += 1
next cycle is executed. In this new cycle, the character "e" is assigned to }
var message = "The string contains \(counter) letters" // 3
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.
Control Transfer Statements 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
Sometimes loops must be interrupted, independently of the state of the
completely, moving the execution of the program to the statements after
condition. Swift offers instructions to break the execution of loops and
the loop. The following example only counts the characters in the string
conditionals. The following are the most frequently used.
that are placed before the first letter "l".
continue—This instruction interrupts the current cycle and moves to Listing 2-66: Interrupting the loop
the next. The system ignores the rest of the statements in the block 
after the instruction is executed. var mytext = "Hello"
var counter = 0
break—This instruction interrupts the loop. The rest of the
statements in the block and any pending cycles are ignored after the for letter in mytext {
if letter == "l" {
instruction is executed. break
}
The continue instruction is applied when we do not want to execute the rest counter += 1
}
of the statements in the block, but we want to keep the loop running. For var message = "The string contains \(counter) letters" // 2
instance, the following code counts the letters in a string but ignores the 
letters "l".
Again, the if statement of Listing 2-66 compares the value of letter with the
Listing 2-65: Jumping to the next cycle of the loop 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
var mytext = "Hello"
instruction is executed, and the loop is over, no matter how many
var counter = 0 characters are left in the string. In consequence, only the characters
located before the first letter "l" are considered (H and e).
for letter in mytext {
if letter == "l" { The break instruction is also useful to cancel the execution of a switch
continue statement. The problem with the switch statement in Swift is that the cases
}
counter += 1 must be exhaustive, which means that every possible value must be
} contemplated. When this is not possible or necessary, we can use the break
var message = "The string contains \(counter) letters" // 3
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
The if statement inside the for in loop of Listing 2-65 compares the value of in the default case for the rest of the values that we do not care about.
letter with the letter "l". If the characters match, the continue instruction is
Listing 2-67: Ignoring values in a switch statement Guard


var age = 19
var message = ""
The guard instruction is intended to prevent the execution of the code that
switch age {
case 13:
follows the statement. For example, we can break the execution of a loop
message = "Happy Bar Mitzvah!" when a condition is satisfied, as we do with an if else statement.
case 16:
message = "Sweet Sixteen!"
case 21: Listing 2-68: Interrupting a loop with guard
message = "Welcome to Adulthood!" 
default:
break var mytext = "Hello"
} var counter = 0

for letter in mytext {
guard letter != "l" else {
After the execution of this code, the message variable is empty because break
there is no case that matches the value of the age variable and therefore the }
counter += 1
code in default is executed and the break instruction returns the control to }
the statements after the switch. var message = "The string contains \(counter) letters" // 2

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.
func myfunction() {
must write the name and the data type separated by a colon. In Listing 3-3, can receive values, but the result produced by processing those values is
the function is declared with one parameter of type Int called number. trapped inside the function. To communicate the result of an operation to
The call must include the name of the parameter and the value we the rest of the code, functions can return a value using an instruction
want to send to the function. When the function of Listing 3-3 is called, the called return. The return instruction finishes the processing of the function,
value between the parentheses of the call (5) is assigned to the number so we must declare it after all the required statements have been
constant, the value of the constant is multiplied by 2, and finally the result processed, as in the following example.
is included in a string with string interpolation.
Of course, we can include as many parameters as we need. The Listing 3-6: Returning a value from a function
following example multiplies two values and creates a string with the 
result.
func doubleValue(number: Int) -> Int {
let total = number * 2
Listing 3-4: Sending multiple values to a function return total
}

let result = doubleValue(number: 25)
func multiply(number1: Int, number2: Int) { let message = "The result is \(result)" // "The result is 50"
let result = number1 * number2 
let message = "The result is \(result)" // "The result is 80"
}
multiply(number1: 20, number2: 4) 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
Functions may not only be called every time we need them, but also the
returned by the function. A function can only return values of the type
values we provide to the function in the call may be different each time.
specified in its definition. For instance, the function in Listing 3-6 can only
This makes functions reusable.
return integer values because we declared the returned type as -> Int.
Listing 3-5: Sending different values to a function 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
func doubleValue(number: Int) { call. For instance, in the code of Listing 3-6, we create the result variable and
let total = number * 2 assign to it a call to the doubleValue() function. When the system processes
let message = "Result: \(total)"
} this statement, the function is executed first and then the value returned
doubleValue(number: 5) // "Result: 10" (50) is assigned to the variable.
doubleValue(number: 25) // "Result: 50"
 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
The constants and variables declared inside a function, like total and message, string and an integer.
are not accessible from other parts of the code. This means that a function
Listing 3-7: Returning a tuple The doubleValue() function in Listing 3-8 is similar to previous examples. It
 receives a number, multiplies it by 2, and returns the result, but this time
func sumCharacters(word: String) -> (String, Int) { we first check that the value received by the function is less than 10. If the
var characters = "" value is equal or higher than 10, the guard instruction calls the return
var counter = 0
for letter in word { instruction with the value received by the function, otherwise, the
characters += "\(letter) " statements of the function are executed as normal. In this case, the value
counter += 1
} sent to the function is 25, therefore the condition is false, and the same
return (characters, counter) value is returned.
}
var (list, total) = sumCharacters(word: "Hello")
Notice that in the example of Listing 3-8 we simplified our code including
var message = "There are \(total) characters (\(list))" the multiplication in the return instruction. The return instruction can take
 single values or expressions like this and it takes care of solving the
expression and returning the result. For this reason, sometimes we may
The sumCharacters() function in Listing 3-7 receives a string (word: String) and find functions with only one statement in charge of returning a value. If
returns a tuple composed of a string and an integer (-> (String, Int)). The this is the case, we can remove the return keyword. In the following
function adds the characters to the characters variable and counts them with example, the call sends the number 25 to the function, the value is
the counter variable, as we did before (see Listing 2-63). At the end, the multiplied by 2, and returned, as in previous examples, but this time we
tuple is returned, its values are assigned to the list and total variables, and didn't have to declare the return keyword because there is only one
then incorporated into a string ("There are 5 characters (H e l l o )"). statement inside the function and therefore the compiler knows what to
Besides returning the result of an operation, the return instruction can also return.
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 Listing 3-9: Removing the return keyword
following example. 
func doubleValue(number: Int) -> Int {
Listing 3-8: Interrupting the execution of a function with guard number * 2
 }
let result = doubleValue(number: 25)
func doubleValue(number: Int) -> Int { let message = "The result is \(result)" // "The result is 50"
guard number < 10 else { 
return number
}
return number * 2 A related keyword is inout. This keyword is used to preserve a value after
} the function finishes processing. When a parameter is marked with inout,
let result = doubleValue(number: 25)
let message = "The result is \(result)" // "The result is 25" any changes performed on the value are stored in the original variable.
 This is useful when we call a function from another function, and we want
the modifications introduced by the second function to persist.
Listing 3-11: Declaring argument labels
Listing 3-10: Modifying external variables from a function 
 func doubleValue(years number: Int) -> Int {
number * 2
func first() {
}
var number = 25
second(value: &number) let result = doubleValue(years: 8)
let message = "The result is \(result)" // "The results is 16"
print("The result is \(number)") // "The result is 50"

}
func second(value: inout Int) {
value = value * 2 The doubleValue() function in Listing 3-11 declares an argument label called
}
first() years for the number parameter. From now on, the name of the parameter
 (number) is the one used by the statements of the function to access the
value received from the call, while the argument label (years) is the one
This code defines two functions: first() and second(). The second() function used when calling the function.
receives an inout parameter called value, which means that any modification If what we want instead is to remove an argument label, we can define it
on its value is stored in the original variable. The first() function defines a with an underscore.
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 Listing 3-12: Removing argument labels
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 func multiply(number1: Int, _ number2: Int) -> Int {
ampersand before the variable's name (&). This tells the system that the number1 * number2
}
variable is going to be modified by the function. let result = multiply(number1: 25, 3)
An important aspect of the definition of a function are the names of the let message = "The result is \(result)" // "The result is 75"
parameters. For example, the function doubleValue() of previous examples 
 Generic Functions
func sayhello(name: String = "Undefined") -> String {
return "Your name is " + name 
}
let message = sayhello() // "Your name is Undefined"
 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
The code in Listing 3-13 declares the function sayhello() with one Overloading and allows us to define multiple functions with the same
parameter of type String called name and with the string "Undefined" as the name to process different types of values.
default value. When the function is called without a value, the string
"Undefined" is assigned to name. Listing 3-14: Declaring different functions with the same name

func getDescription(value: Int) -> String {
let message = "The value is \(value)"
return message
}
func getDescription(value: String) -> String {
let message = "The value is \(value)"
return message
}
let result1 = getDescription(value: 3) // "The value is 3"
let result2 = getDescription(value: "John") // "The value is John"

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 very limited due to the impossibility of the compiler to know the
only one function with a generic data type. nature of the values received. For example, we can add two integers,
Generic data types are placeholders for real data types. When the function but we cannot add two Boolean values. To solve these issues, we can
is called, the generic data type is turned into the data type of the value constrain the generic data types with protocols. We will study how
received. If we send an integer, the generic data type turns into an Int, if we to define protocols and how to use them later in this chapter.
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>).
included in a library called Standard Library. The Standard Library includes let absolutenumber = abs(-25)
let minnumber = min(absolutenumber, 100)
everything, from operators to primitive data types, as well as predefined print("The number is: \(minnumber)") // "The number is: 25"

functions. The following are some of the most frequently used.
The code in Listing 3-16 implements the abs() function to calculate the
print(String)—This function prints a string on the Xcode’s console.
absolute value of -25, then gets the smallest value between absolutenumber
abs(Value)—This function returns the absolute value of an integer. and the number 100 with the min() function, and finally prints a message on
max(Values)—This function compares two or more values and the console with the result.
returns the largest. As we will see later, sequences and collections of values are very important
min(Values)—This function compares two or more values and in computer programming. The strings studied in Chapter 2 are a clear
returns the smallest. example. A string is a sequence of values of type Character. The Swift
Standard Library includes a few functions to quickly create sequences of
There are also functions available to stop the execution of the application values our application may need to process information. The following are
in case of an unrecoverable error. some of the most frequently used.
fatalError(String)—This function stops the execution of the stride(from: Value, through: Value, by: Value)—This function
application and prints on the console the message provided by the returns a collection of values from the value specified by the from
argument. argument to the value specified by the through argument in intervals
specified by the by argument.
stride(from: Value, to: Value, by: Value)—This function returns a of the finalsequence collection and print them on the console ("Hello - 0",
collection of values from the value specified by the from argument to "Hello - 2", "Hello - 4", "Hello - 6", "Hello - 8").
the value specified by the through argument in intervals specified by
the by argument. The last value is not included.
repeatElement(Value, count: Int)—This function returns a
collection with the number of elements specified by the count
argument and with the value specified by the first argument.
zip(Collection, Collection)—This function returns a collection of
tuples containing the values of the collections provided by the
arguments in sequential order.
Scopes first()
second()

print("Total: \(total)") // "Total: 29.5"

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 This example declares two variables in the global space, multiplier and total,
(statements in braces) to enclose their functionality. Blocks are and two functions with local constants. The multiplier and total variables are
independent processing units; they contain their own statements and global and therefore they are accessible from anywhere in the code, but
variables. To preserve their independence and avoid conflicts between the constants defined inside the functions are available only to the
these units and the rest of the code, their variables and constants are statements inside the function in which they were created. Therefore, the
isolated. Variables and constants declared inside a block are not accessible
base constant declared inside the first() function is only accessible from this
from other parts of the code; they can only be used inside the block in
function (neither the statements in the global space nor other functions or
which they were created.
blocks outside first() have access to it), but we can modified the value of total
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 from this function because it is a global variable.
referred as global space or local space). The variables and constants The next function, second(), declares a new constant called multiplier. This
outside a block have global scope, while those declared inside a block have constant has the same name as the multiplier variable declared before in the
local scope. The variables and constants with global scope are accessible global space, but they have different scopes and therefore they are
from any part of the code, while those with local scope are only accessible assigned different locations in memory and can contain different values.
from the statements inside the block in which they were created (and the When we read the value of multiplier in the second() function to add a new
statements from blocks created inside their block). To better understand value to the total variable, the multiplier that the system reads is the one
how scopes are defined in code, here is a practical example. declared inside the function because in that space this constant has
precedence over the global variable with the same name, which shows
Listing 3-18: Using variables and constants of different scopes
that we can declare variables and constants with the same name as long as

they have different scopes.
var multiplier = 1.2
var total = 0.0
func first() {
let base = 10.0
total += base * multiplier
}
func second() {
let multiplier = 5.0
let base = 3.5
total += base * multiplier
}
Closures 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
Blocks of code, such as those used to create functions, conditionals, and executes the closure and then assigns the value returned by the closure to
loops, have their own scope, and know the variables that are available to the constant or variable.
them. Because of this, we can generate independent processing units that
do not interfere with the operations of other units. This feature is so Listing 3-20: Initializing a variable with the value returned by a closure
important in computer programming that Swift offers the possibility to 
create independent blocks called Closures to take advantage of it. let myaddition = { () -> Int in
var total = 0
Closures are simple blocks of code with the syntax { (parameters) -> Type in let list = stride(from: 1, through: 9, by: 1)
statements }. They are like functions (functions are closures with a name), but
for number in list {
they are wrapped with braces and the in keyword is included to separate total += number
the data types from the statements. }
return total
Closures can be assigned to variables and executed using the name of the }()
variable, as we do with functions. The name of the variable becomes the print("The total is \(myaddition)") // "The total is 45"

name of the closure, as shown next.
The closure declared in Listing 3-20 doesn't receive any value and returns
Listing 3-19: Assigning closures to variables
an integer (() -> Int). The code in the closure adds the values of a collection

let multiplier = { (number: Int, times: Int) -> Int in
(1 to 9) and returns the result, but because we included the parentheses at
let total = number * times the end of the definition, the value assigned to the myaddition constant is
return total
}
the one returned by the closure (45), not the closure itself.
print("The result is \(multiplier(10, 5))") // "The result is 50" 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
This example defines a closure and assigns it to the multiplier constant. After
can also be removed.
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
Listing 3-21: Simplifying a closure
the same syntax as functions ((number: Int, times: Int) -> Int), but the

parameters' names are not turned into argument labels and therefore they
let myaddition: Int = {
are ignored in the call. var total = 0
let list = stride(from: 1, through: 9, by: 1)
The closure in the previous example was defined in the global space and
for number in list {
total += number 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.
return total
}()
print("The total is \(myaddition)") // "The total is 45" Listing 3-23: Assigning the closure to the function's argument


func processclosure(myclosure: (Int, Int) -> Int) {
Closures cannot only be assigned to constants and variables but also sent
print("The total is: \(myclosure(10, 2))") // "The total is: 20"
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 processclosure(myclosure: { (number: Int, times: Int) -> Int in
return number * times
closure receives and returns, as in the following example. })

Listing 3-22: Sending a closure to a function
 The code in Listing 3-23 works the same way as the previous example, but
let multiplier = { (number: Int, times: Int) -> Int in it was simplified by assigning the closure directly to the function's
let total = number * times
return total
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
func processclosure(myclosure: (Int, Int) -> Int) {
let total = myclosure(10, 2)
can declare the closure at the end of the call, as in the following example.
print("The total is: \(total)") // "The total is: 20"
}
processclosure(myclosure: multiplier)
Listing 3-24: Using Trailing Closures
 
func processclosure(myclosure: (Int, Int) -> Int) {
The first statement in Listing 3-22 defines a closure that multiplies two print("The total is: \(myclosure(10, 2))") // "The total is: 20"
}
integers and returns the result. A function that receives a closure of this processclosure() { (number: Int, times: Int) -> Int in
number * times
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() When we pass the closure this way, the call does not include the myclosure
function is called in the last statement, the value of the multiplier variable is argument anymore. The closure declared after the parentheses is
considered to be the last argument of the function and therefore the
sent to the function. The function assigns the closure to the myclosure
argument label is not necessary.
constant, and the closure is executed inside the function using this name
The code in Listing 3-24 works the same way as previous examples, the
and the values 10 and 2, producing the result 20.
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 var myclosure: () -> Void = {}
function (or in this case a closure) includes only one statement, the
func passclosure(closure: @escaping () -> Void) {
compiler implies that the value produced by that statement is the one to myclosure = closure
return and therefore the return keyword is not required anymore. But when }
passclosure() { () -> Void in
we are passing the closure to a function, Swift can also infer the data types print("Closure Executed")
of the values received by the closure and therefore we don't have to }
myclosure()
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 In the code of Listing 3-26, we declare a variable called myclosure that stores
represented by $0, the second value by $1, and so on. 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
Listing 3-25: Inferring the closure's data types 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
func processclosure(myclosure: (Int, Int) -> Int) { closure that prints a value on the console, so now the myclosure variable
print("The total is: \(myclosure(10, 2))") // "The total is: 20"
} contains that closure. Finally, we execute the closure in myclosure and the
processclosure() { $0 * $1 } message is printed on the console.

Structures are an essential part of the organizational paradigm proposed To define a new structure, we must use the struct keyword and enclose the
by Swift. They are custom data types that include not only data but also data and functionality in braces.
the code in charge of processing that data. When we define a structure,
what we are doing is declaring a data type that may contain variables and Listing 3-27: Defining a structure
constants (called properties) and functions (called methods). Later we can 
declare variables and constants of this type to store information with the struct Item {
var name: String = "Not defined"
characteristics defined by the structure. These values (called instances) will var price: Double = 0
be unique, each one with its own properties and methods. }

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 instead of modifying the properties of the instance, the system
variable. creates a new instance and assigns the values to the properties of
In this last example, the properties of a new instance always take the that instance. For this to be possible, the structure must be stored in
values declared in the structure’s definition ("Not Defined" and 0), but we can a variable so it can be replaced by the new structure later.
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 Structures may be instantiated inside other structures, as many times as
them, we must mention the structure they belong to. The syntax necessary. The dot notation is extended in these cases to reach every
implements dot notation, as in variable.property, where variable is the name of element in the hierarchy.
the variable that contains the instance of the structure and property is the
name of the property we want to access. Listing 3-30: Structures inside structures

Listing 3-29: Assigning new values to the properties of a structure struct Price {
 var USD = 0.0
var CAD = 0.0
struct Item { }
var name = "Not defined" struct Item {
var price = 0.0 var name: String = "Not defined"
} var price: Price = Price()
var purchase = Item() }
purchase.name = "Lamps" var purchase = Item()
purchase.price = 10.50 purchase.name = "Lamps"
purchase.price.USD = 10.50
print("Product: \(purchase.name) $ \(purchase.price)") 

Listing 3-30 defines two structures: Price and Item. The Item structure
In this example, the properties of the Item structure and the purchase
contains the same properties as before, but now the data type of the price
variable are declared as before, but this time we let Swift infer their data
property is Price, which means that instead of storing a single value, this
types. After the instance is created, new values are assigned to its
property can now store a structure that in turn contains two properties:
properties using dot notation. Dot notation is not only used to assign new
USD and CAD. When the Item structure is created and assigned to the
values but also to read the current ones. At the end, we read and print the
purchase variable, the Price structure for the price property is also created
values of the name and price properties on the console ("Product: Lamps $
with its values by default.
10.5").
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
IMPORTANT: Notice that we have stored the structure in a variable
of Listing 3-30 accesses the USD property of the price structure inside the
(var). This is to be able to assign new values to its properties later.
purchase structure to assign a price to the item in American Dollars
When the values of the properties in a structure are modified,
(purchase.price.USD = 10.50).
In this example, the price structure is created during instantiation, but this is returns nil, but when all the optionals have values, the instruction performs
not usually the case. Sometimes the values of the properties containing the task. (In this case, it assigns the number 10.50 to the USD property.)
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.
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
Methods perform the task, no matter how complex it is. And second, we do not have
to write the operation over and over again, because it is always part of the

instance of the structure we are working with.
A method can read the values of the instance’s properties but cannot
If we could only store properties, structures would be just complex data
assign new values to them. If we want a method to be able to modify the
types, like tuples, but structures can also include code. This is done
values of the properties of its own instance, we must declare the mutating
through functions. Functions inside structures are called methods, but the
keyword before the func keyword in the structure's definition.
definition and functionality are the same.
The syntax to execute a method is variable.method(), where variable is the
Listing 3-35: Assigning new values to properties from inside the structure
name of the variable that contains the instance of the structure and method

is the name of the method we want to call inside that structure, as shown
struct Item {
in the following example. var name = "Not defined"
var price = 0.0
Listing 3-34: Defining methods mutating func changename(newname: String) {
 name = newname
struct Item { }
}
var name = "Not defined"
var purchase = Item()
var price = 0.0
purchase.changename(newname: "Lamps")
func total(quantity: Double) -> Double {
print("Product: \(purchase.name)") // "Product: Lamps"
return quantity * price
} 
}
var purchase = Item() The changename() method of the Item structure in Listing 3-35 is declared as a
purchase.name = "Lamp"
purchase.price = 10.50 mutating method so it can assign a new value to the name property.
Therefore, we do not need to modify the name property directly, we can call
print("Total: \(purchase.total(quantity: 2))") // "Total: 21.0"

this method with the value we want to store, and the method takes care of
assigning the value to the property.
In Listing 3-34, a method is declared as part of the definition of the Item
structure. The method receives a value representing the number of items
sold and calculates the total money spent in the transaction. We could
have performed this operation outside the structure by reading the value
of the price property, but having a method in the structure itself presents
some advantages. First, we don't have to worry about how the method
calculates the value; we just call the method with the right value and let it
Initialization Listing 3-37: Using memberwise initializers to provide the initial values of a
structure


struct Item {
Every instance created from the structure’s definition has the purpose to var name: String
store and process specific data. For example, we can create multiple var price: Double
}
instances of our Item structure to store information about different var purchase = Item(name: "Lamp", price: 10.50)
products. Each product will have its own name and price, so the properties print("Purchase: \(purchase.name) $ \(purchase.price)")
of each instance must be initialized with the proper values. The 
property and then multiply this value by the corresponding exchange rate }
}
to get the value of the CAD property (the same price in Canadian dollars). var myprice = Price(canadians: 5)
As well as with any other method or function, the init() method may include 
parameters. These parameters are used to specify initial values from the
initializer. As explained before, Swift identifies each function by its name and
parameters, so we can declare functions with the same name as long as
Listing 3-39: Declaring the parameters to initialize the structure they have different parameters. In the example of Listing 3-40, two init()
 methods were declared to initialize the instance of the structure. The first
struct Price { method receives a Double value with the name americans and the second
var USD: Double method also receives a Double value but with the name canadians. The right
var CAD: Double
method will be executed according to the argument included in the
init(americans: Double) { initializer. In this example, we use the argument canadians with the value 5,
USD = americans so the instance is initialized by the second init() method.
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
Computed Properties calculate the value every time the property is read. No matter if the value
of the ratetoCAD property changes, the canadians property will always return

the right price in Canadian dollars.
Computed properties with only a getter are called read-only properties
The properties we have declared up to this point are called Stored
because we can only read their values. When we declare a read-only
Properties. Their function is to store a value in memory. But there are other
property, we can omit the get method. In addition, as we have seen before,
types of properties called Computed Properties. These properties do not
when a block contains only one statement, it knows what to return, so we
store a value of their own, instead they have access to the rest of the
can omit the return keyword as well. The previous example can therefore be
properties of the structure and can perform operations to set and retrieve
simplified as follows.
their values.
Two methods are available for computed properties to be able to set
Listing 3-42: Defining read-only properties
and retrieve a value: get() and set(). These methods are also called getters

and setters and are declared in braces after the property’s name. Although
struct Price {
both methods are useful, only the get method is required. var USD: Double
var ratetoCAD: Double
set { }
USD = newValue * ratetoUSD var purchase = Price(USD: 11, ratetoCAD: 1.29, ratetoUSD: 0.7752)
} 
}
}
var purchase = Price(USD: 11, ratetoCAD: 1.29, ratetoUSD: 0.7752)
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
Type Properties and Methods property is a type property, only accessible from the type itself. The same
happens with methods, as shown next.

Listing 3-47: Defining type methods
The properties and methods declared above are accessible on the

instances created from the definition of the structure. This means that we
must create an instance of the structure to be able to read and modify struct Price {
var USD: Double
their values. But there are times when being able to execute properties var CAD: Double
and methods from the definition itself makes sense. We might need, for
example, to get information related to all instances, or call methods to static func reserved() -> Price {
return Price(USD: 10.0, CAD: 11.0)
create instances with standard values. In Swift, this is possible by declaring }
type properties and methods. These are properties and methods }
accessible from the data type, not the instance created from that type. var reservedprice = Price.reserved()
print("Price in USD: \(reservedprice.USD) CAD: \(reservedprice.CAD)")
Type properties and methods for structures are declared adding the 
static keyword to the definition. Once a property or method is declared with
this keyword, they are only accessible from the definition itself. In the The structure in this example includes a type method called reserved().
following example, we include a type property called currencies to inform The method creates and returns an instance of the Price structure with
how many currencies the structures can handle. standard values. This is a common procedure and another way to create
our own initializer. If we use the initializer by default, the values must be
Listing 3-46: Defining type properties provided every time the instance is created, but with a type method all we
 need to do is to call the method on the type to get in return an instance
struct Price { configured with specific values. In our example, the values correspond to a
var USD: Double
var CAD: Double reserved price. We call the reserved() method on the Price type, the method
creates an instance of the Price structure with the values 10.0 and 11.0, and
static var currencies = 2
} then this instance is assigned to the reservedprice variable. At the end, the
print(Price.currencies) // 2 values of both properties are printed on the console to confirm that their

values were defined by the reserved() method. (Again, the method is not
accessible from the instance, only from the data type.)
As illustrated by the code in Listing 3-46, there is no need to create an
instance to access a type property or method. After the definition, the
currencies property is read using the name of the structure and dot notation
(Price.currencies). If we create an instance from this definition, the only
properties accessible from the instance will be USD and CAD. The currencies
Generic Structures angle brackets, but this is only required when the initialization doesn't
include any value. For example, the following code creates an instance of
 the same structure but with a string and let Swift infer the generic data
type from the value.
At the beginning of this chapter, we explained how to create generic
functions. These are functions that can process values of different data Listing 3-49: Using generic structures
types. The function defines a placeholder for the data type and then 
adopts the data type of the value it receives. But generics data types are struct MyStructure<T> {
not exclusive to functions, we can also turn data types themselves, such as var myvalue:T
structures, into generic types. The advantage is that we can create func description() {
independent processing units that can handle different types of values. To print("The value is: \(myvalue)") // "The value is: Hello"
create a generic structure, we must declare the generic data type after the }
}
name of the structure and between angle brackets, as we did for functions. let instance = MyStructure(myvalue: "Hello")
instance.description()

Listing 3-48: Defining generic structures

IMPORTANT: These are basic examples of how to create and work
struct MyStructure<T> {
var myvalue:T with generic data types. As with functions, generics only become
useful when we constrain the data using protocols. We will learn
func description() {
print("The value is: \(myvalue)") // "The value is: 5" more about generics in the following sections and study protocols at
} the end of this chapter.
}
let instance = MyStructure<Int>(myvalue: 5)
instance.description()

Primitive Type Structures The structures for these data types are defined in the Swift Standard
Library. All we need to do to create an instance is to implement the
 initializer with the value we want to store.
Including properties and methods inside a structure and then assigning an Listing 3-50: Initializing variables with standard initializers
instance of that structure to a variable is a simple way to wrap data and 
functionality in a single portable unit of code. Structures are usually used var mynumber = Int(25)
this way, as practical wrappers of code, and Swift takes advantage of this var myprice = Double(4.99)

feature extensively. In fact, all the primitive data types defined in Swift are
structures. The syntax variable: Int = value, for example, is a shortcut provided This is the same as assigning the values directly to the variable (e.g., var
by Swift for the initializer variable = Int(value). Every time we assign a new myprice = 4.99), but these initializers become useful when the value we want
value to a variable of a primitive data type, we are assigning a structure to assign to the structure is of different type. The definitions of these
that contains that value. The following are the initializers of some of the structures include several initializers that convert the value to the right
primitive data types studied in Chapter 2. 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
Int(Value)—This is the initializer of the Int data type. The argument is converts those numbers to the most comprehensive type and performs the
the value we want to assign to the instance. If no value is provided, operation, but variables are already of a specific type and therefore they
the value 0 is assigned by default. Initializers for similar types are also must be explicitly converted before the operation is performed or we get
available (Int8(), Int16(), Int32(), and Int64()). an error. (The process does not really convert the variable; it just creates a
UInt(Value)—This is the initializer of the UInt data type. The new value of the right type.)
argument is the value we want to assign to the instance. If no value is
provided, the value assigned is 0. Initializers for similar types are also Listing 3-51: Casting a variable
available (UInt8(), UInt16(), UInt32(), and UInt64()). 
var number1: Int = 10
Float(Value)—This is the initializer of the Float data type. The var number2: Double = 2.5
argument is the value we want to assign to the instance. If no value is var total = Double(number1) / number2 // 4.0

provided, the value 0.0 is assigned by default.
Double(Value)—This is the initializer of the Double data type. The The variables number1 and number2 defined in Listing 3-51 are of type Int
argument is the value we want to assign to the instance. If no value is and Double. To perform a division between them we must cast one of them
provided, the value assigned is 0.0. 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 random(in: Range)—This type method returns a random number.
operation. (The value 10.0 created by the initializer is divided by the value The value is calculated from a range of integers provided by the in
2.5 of number2 to get the result 4.0.) The process is described as "casting the argument.
number1 variable to a Double".
negate()—This method inverts the sign of the value.
These initializers are also useful when working with String values.
Sometimes the characters of a string represent numbers that we need to
isMultiple(of: Int)—This method returns true if the value is a
process. The problem is that strings cannot be processed as numbers. We multiple of the value provided by the of argument (this is similar to
cannot include a string in an arithmetic operation without first converting what we can achieve with the % operator).
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 The min and max properties are especially useful because they allow us
into a number. If the operation cannot be performed, the initializer returns to determine whether an operation could overflow a variable (produce a
nil, so we can treat it as an optional value. In the following example, we
result that is greater or lesser than the minimum and maximum allowed).
convert the string "45" into the integer 45 and add the value 15 to it.
Listing 3-53: Checking the maximum possible value for the Int8 type

Listing 3-52: Extracting numbers from strings
var mynumber: Int8 = 120

let increment: Int8 = 10
var units = "45"
if (Int8.max - mynumber) >= increment { // (127 - 120) >= 10
if let number = Int(units) { mynumber += increment
let total = number + 15 }
print("The total is \(total)") // "The total is 60" print(mynumber) // "120"
} 

This example takes advantage of the max property to make sure that
The structures defined for primitive data types also have their own
incrementing the value of a variable will not overflow the variable (the
properties and methods. This includes type properties and methods. For
result will not be greater than the maximum value the variable can
instance, the following are the most frequently used properties and
handle). The code starts by defining a variable of type Int8 to store the
methods provided by the structures that process integer values (e.g., Int).
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
min—This type property returns the minimum value the data type maximum value admitted by an Int8 variable (Int8.max – mynumber) and
can handle.
compare this result with the value of increment. If the number of units we
max—This type property returns the maximum value the data type have left is greater or equal than the value of increment, we know that the
can handle. operation can be performed without going over the limit. (In this example,
the operation is not performed because the addition of 120 + 10 produces var mynumber: Double = 2.890
mynumber = mynumber.rounded(.toNearestOrAwayFromZero)
a result greater than the limit of 127 admitted by Int8.) print("The round number is \(mynumber)") // "The round number is 3.0"
The type Double also includes its own selection of properties and 
infinity—This type property returns an infinite value. toggle()—This method toggles the value. If the value is true, it
minimum(Double, Double)—This type method compares the becomes false and vice versa.
values provided by the arguments and returns the minimum.
random()—This type method returns a random Bool value.
maximum(Double, Double)—This type method compares the
values provided by the arguments and returns the maximum. The following example checks the current value of a variable and assigns
random(in: Range)—This type method returns a random number. the value false if it is true, or vice versa.
The value is calculated from a range of values of type Double provided
by the in argument. Listing 3-55: Modifying the value of a Bool variable

negate()—This method inverts the sign of the value. var valid: Bool = true
squareRoot()—This method returns the square root of the value. if valid {
print("It is Valid")
remainder(dividingBy: Double)—This method returns the valid.toggle()
}
remainder produced by dividing the value by the value specified by print(valid) // false
the dividingBy argument. 
In this case, the most useful method is probably rounded(). With this
method, we can round a floating-point value to the nearest integer.
When we declare a range using these operators, Swift creates the proper We can also invert the loop with the reversed() method.
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 Listing 3-57: Inverting a range
a closed range. These structures provide common properties and methods 
to work with the range. The following are the most frequently used. var message = ""
var range = 0..<10
lowerBound—This property returns the range's lower value (the for item in range.reversed() {
message += "\(item) "
value on the left). }
print(message) // "9 8 7 6 5 4 3 2 1 0 "
upperBound—This property returns the range's upper value (the 
value on the right).
This example creates a range from 0 to 9 with the ..< operator and then As mentioned before, ranges are used by the random() method to get a
calls the reversed() method to invert it. This method creates a collection with random value. The following example generates a loop that calculates
the values in reversed order, so we can read it with a for in loop. The multiple random values from 1 to 10. The condition stops the loop when
statement inside the loop adds the values to the message string, and this the number returned by the method is equal to 5. Inside the loop, we also
string is printed on the console to confirm that the values were effectively increment the value of the attempts variable to calculate the number of
reversed. cycles required for the random() method to return our number.
Ranges can also simplify switch statements that have to consider multiple
values per case. Listing 3-59: Calculating random numbers

Listing 3-58: Using range operators in a switch statement var mynumber: Int = 0
 var attempts = 0
and methods to work with indexes and access the characters of a string. prefix(upTo: Index)—This method returns a string created from the
The following are the most frequently used. first character of the original string to the character at the index
indicated by the upTo argument, but without including this last
startIndex—This property returns the index value of the first character.
character of the string. replaceSubrange(Range, with: String)—This method replaces the
endIndex—This property returns the index value of one position characters in the position determined by the range provided as the
after the last character of the string. It is useful to manipulate range of first argument with the string provided by the with argument.
characters, as we will see later. removeSubrange(Range)—This method removes the characters in
firstIndex(of: Character)—This method returns the index where the positions determined by the range specified by the argument.
the character specified by the of argument appears for the first time
in the string. Strings are collection of values. To access a specific character in a string, we
lastIndex(of: Character)—This method returns the last index where must declare the Index structure with the index of the character we want to
the character specified by the of argument appears in the string. read after the name of the variable that contains the string, enclosed in
square brackets, as in the following example.
insert(Character, at: Index)—This method inserts into the string
the character provided by the first argument at the position
Listing 3-61: Processing the string’s characters
determined by the at argument.

insert(contentsOf: String, at: Index)—This method inserts into var text = "Hello World"
the string the value of the contentsOf argument at the position if !text.isEmpty {
let start = text.startIndex
determined by the at argument. let firstChar = text[start]
remove(at: Index)—This method removes and returns the print("First character is \(firstChar)") // "First character is H"
character at the position determined by the at argument. }

prefix(Int)—This method returns a string created from the first
character of the original string to the maximum length determined by The first thing we do in this example is to check the value of the isEmpty
the attribute. property to make sure the string is not empty and there are characters to
prefix(through: Index)—This method returns a string created from read (notice the ! operator to invert the condition). Once we know that
the first character of the original string to the character at the index there are characters to work with, we get the index of the string’s first
indicated by the through argument. 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 If we wanted to get the previous index, we could have specified a negative
the value returned by startIndex. The trick is that, since Index values are not number of units for the offset value, but another way to move forward and
integers, we cannot just add a number to them. Instead, we must use the backward is to implement the other versions of the index() method. The
methods provided by the String structure. following example gets the next index after the initial index and prints the
corresponding character on the console.
index(after: Index)—This method increments the index specified by
Listing 3-63: Getting the next index
the after argument one unit and returns a new Index value with the

result.
var text = "John"
index(before: Index)—This method decrements the index specified let start = text.startIndex
var next = text.index(after: start)
by the before argument one unit and returns a new Index value with
the result. print("Second letter is \(text[next])") // "Second letter is o"

index(Index, offsetBy: Int)—This method increments the index
specified by the first argument the amount of units specified by the Once the right index is calculated, we can call some of the String methods
offsetBy argument and returns a new Index value with the result. to insert or remove characters. The insert() method, for instance, inserts a
single character at the position indicated by the second argument. In the
The following example advances the initial index 6 positions to get a following example, we call it with the value of endIndex to add a character at
different character. the end of the string (endindex points to the position after the last
character).
Listing 3-62: Calculating a specific index
 Listing 3-64: Inserting a character in a string
var text = "Hello World" 
if text != "" { var text = "Hello World"
let start = text.startIndex text.insert("!", at: text.endIndex)
let newIndex = text.index(start, offsetBy: 6)
print("New string is \(text)") // "New string is Hello World!"
print("The character is \(text[newIndex])") // "The character is W" 
}

If we do not know where the character is located, we can find the index
The index() method applied in Listing 3-62 takes an integer to calculate the with the firstIndex() method. The value returned by this method is an
new index. The original index is incremented the number of units indicated optional containing the Index value of the first character that matches the
by the integer and the resulting Index value is returned. With this index, we argument or nil if no character is found. In the following example, we
get the character at the position 6 (indexes start from 0). implement it to find the first space character and remove it with the
remove() method.
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.
items. On these terms, we can say that the code in Listing 3-70 declares an
array of three elements of type Int. The += operator adds an array at the end of another array. In Listing 3-73,
An index is automatically assigned to each value starting from 0, and as we use it to add two more elements to the array declared in the first
with strings, we must specify the index of the value we want to read statement. The += operator concatenates the two arrays and assigns the
surrounded by square brackets. 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.
Listing 3-71: Reading the array’s elements
 Listing 3-74: Concatenating two arrays
var list = [15, 25, 35] 
print(list[1]) // 25
var list1 = [15, 25, 35]

var list2 = [45, 55, 65]
var final = list1 + list2 // [15, 25, 35, 45, 55, 65]
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. It is possible to declare arrays of arrays. These types of arrays are called
Multidimensional Arrays. Arrays inside arrays are listed separated by
Listing 3-72: Assigning a new value to an element comma.

Listing 3-75: Creating multidimensional arrays var total = 0
let list = [15, 25, 35]

var list: [[Int]] = [[2, 45, 31], [5, 10], [81, 12]] for value in list {
 total += value
}
print("The total is \(total)") // "The total is 75"
This example creates an array of arrays of integers. (Notice the declaration 
of the array inside another array [[Int]].) To access the values, we must
declare the indexes of each level in square brackets, one after another. The The code in Listing 3-78 implements a for in loop to add the numbers in the
following example returns the first value (index 0) of the second array list array to the total variable. At the end, we print the result. Although this is
(index 1). The instruction looks for the array at index 1 and then gets the a legit way to do it, arrays offer multiple properties and methods to read
number at index 0. and process their values.
Listing 3-76: Reading values from a multidimensional array count—This property returns the total number of elements in the
 array.
var list: [[Int]] = [[2, 45, 31], [5, 10], [81, 12]] isEmpty—This property returns a Boolean value that indicates if the
print(list[1][0]) // 5
 array is empty.
first—This property returns the first element of the array or nil if the
To remove all the elements from an array, we can assign to the variable
array is empty.
one of the initializers introduced before or just the square brackets with no
values. last—This property returns the last element of the array or nil if the
array is empty.
Listing 3-77: Removing the elements of an array append(Element)—This method adds the value specified by the

argument at the end of the array.
var list = [15, 25, 35]
list = [] insert(Element, at: Int)—This method adds a new element to the
 array. The first argument is the value we want to assign to the new
element, and the at argument represents the position of the array
Arrays are collections of values and therefore we can iterate over their where we want to insert the element.
values with a for in loop, as we did with strings before. remove(at: Int)—This method removes an element from the array
at the index specified by the at argument.
Listing 3-78: Reading an array with a for in loop

removeFirst()—This method removes the first element of the array. sorted()—This method returns an array with the elements of the
It returns the value of the element deleted. array in ascending order.
removeLast()—This method removes the last element of the array. sorted(by: Closure)—This method returns an array with the
It returns the value of the element deleted. elements of the array in the order determined by the closure provided
removeAll(where: Closure)—This method removes the elements to the by argument.
in the array that meet the condition established by the closure randomElement()—This method randomly selects an element from
assigned to the where argument. the array and returns it. If the array is empty, the value returned is nil.
removeSubrange(Range)—This method removes a range of shuffled()—This method returns an array with the elements of the
elements from the array. The argument is a range of integers array in random order.
representing the indexes of the elements to remove. reversed()—This method returns an array with the elements of the
replaceSubrange(Range, with: Array)—This method replaces a array in reverse order.
range of elements with the elements of the array provided by the swapAt(Int, Int)—This method exchanges the values of the
with argument. The first argument is a range of integers elements at the indexes specified by the arguments.
corresponding to the indexes of the elements we want to replace.
joined(separator: String)—This method returns a string that
dropFirst(Int)—This method removes the number of elements includes all the values in an array of strings joined by the string
specified by the argument from the beginning of the array. If no specified by the separator argument.
amount is declared, only the first element is removed.
filter(Closure)—This method filters an array and returns another
dropLast(Int)—This method removes the number of elements array with the values that passed the filter. The argument is a closure
specified by the argument from the end of the array. If no amount is that processes the elements and returns a Boolean value indicating
declared, only the last element is removed. whether the value passed the filter or not.
enumerated()—This method is used to iterate over the elements of map(Closure)—This method returns a new array containing the
the array. It returns a tuple containing the index and the value of the results of processing each of the values of the array.
current element.
compactMap(Closure)—This method returns a new array
min()—This method compares the values of the elements and containing the results of processing each of the values of the array,
returns the smallest. but ignores the values that produce a nil result.
max()—This method compares the values of the elements and reduce(Value, Closure)—This method sends the values of the array
returns the largest. to the closure one by one and returns the result of the operation. The
first argument is the value that is going to be processed with the first since the array’s indexes start from 0, we added 1 to myindex to start
value of the array. counting from 1.
contains(where: Closure)—This method returns a Boolean that Another useful property is count. As mentioned before, we can access each
determines if the array contains an element that meets the condition element of the array with the index between square brackets. But trying to
in the closure. 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
allSatisfy(Closure)—This method returns a Boolean value that
than 0 and less than the total amount of elements in the array using this
determines if all the elements in the array comply with the requisites
property.
of a closure.
difference(from: Array)—This method returns a CollectionDifference Listing 3-80: Checking whether an array contains a value in a specific index
structure containing all the changes that have to be performed to 
synchronize the array with the array provided by the from argument. let ages = [32, 540, 12, 27, 54]
This method can work in conjunction with the applying() method to let index = 3
if index > 0 && index < ages.count {
apply all the changes in the array at once. print("The value is: \(ages[index])") // "The value is: 27"
}

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 The methods to add and remove elements from an array are
element, not its index. An alternative is provided by the enumerated() straightforward. The following example illustrates how to implement them.
method, designed to work with these types of loops. Each cycle returns a
tuple with the index and the value of the current element. Listing 3-81: Adding and removing elements

Listing 3-79: Reading indexes and values of an array var fruits = ["Banana", "Orange"]
if !fruits.isEmpty {

fruits.append("Apple") // ["Banana", "Orange", "Apple"]
let fruits = ["Banana", "Orange", "Apple"] fruits.removeFirst() // "Banana"
var message = "My fruits:" fruits.insert("Pear", at: 1) // ["Orange", "Pear", "Apple"]
fruits.insert(contentsOf: ["Cherry", "Peach"], at: 2)
for (myindex, myfruit) in fruits.enumerated() { // ["Orange", "Pear", "Cherry", "Peach", "Apple"]
message += " \(myindex + 1)-\(myfruit)" }
} 
print(message) // "My fruits: 1-Banana 2-Orange 3-Apple"

IMPORTANT: Every time an array is modified, its indexes are
This example uses the constants myindex and myfruit to capture the values reassigned. For instance, if you remove the first element of an array
produced by the enumerated() method and generates a string. Notice that of three elements, the index 0 is reassigned to the second element
and the index 1 to the third element. The system makes sure that compare it against nil or use optional binding before processing it, as in the
the indexes are always consecutive and start from 0. following example.
A more complex method is removeAll(where:). This method removes several Listing 3-84: Selecting a random value from an array
elements at once, but only those that meet a condition. The condition is 
established by a closure that processes each of the values in the array and let fruits = ["Banana", "Orange", "Apple"]
if let randomValue = fruits.randomElement() {
returns true or false depending on whether the value meets the condition or print("The selected value is: \(randomValue)")
not. In the following example, we compare each value with the string }

"Orange" and therefore all the values "Orange" are removed from the
array.
Another random operation is performed by the shuffled() method. With this
Listing 3-82: Removing all the elements that meet a condition method we can randomly sort the elements of an array.

var fruits = ["Banana", "Orange", "Apple", "Orange"]
Listing 3-85: Changing the order of the elements of an array
fruits.removeAll(where: { value in 
value == "Orange"
var fruits = ["Banana", "Orange", "Apple"]
})
fruits = fruits.shuffled()
print(fruits) // ["Banana", "Apple"]
print(fruits) // e.g., ["Orange", "Apple", "Banana"]


Another method that works with a closure is contains(where:). In the following Besides working with all the elements of an array, we can do it with a range
example, we use this method to determine whether an array contains a of elements.
value greater than 60 or not.
Listing 3-86: Reading a range of elements
Listing 3-83: Finding if an element meets a condition 
 var fruits = ["Banana", "Orange", "Apple", "Cherry"]
var list = [55, 12, 32, 5, 9] var someFruits = fruits[0..<2] // ["Banana", "Orange"]
let found = list.contains(where: { value in
value > 60 print("The new selection has \(someFruits.count) fruits")
}) 
print(found) // false
 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
We can also select a random value with the randomElement() method. This with 4 elements and someFruits with 2.
method selects a value from the array and returns an optional, so we must
Arrays created from a range of indexes are of type ArraySlice. This is another achieve. The Array structure includes an initializer that takes two
collection type provided by Swift to store temporary arrays that are arguments, repeating and count, and generates an array with the number of
composed of elements taken from other arrays. We can iterate over these elements indicated by count and the value indicated by repeating.
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 Listing 3-89: Initializing an array with elements of the same value

for persistent storage, we must cast them as Array types using the Array()
var fruits = ["Banana", "Orange", "Apple"]
initializer. The initializer takes the values in the ArraySlice variable and
returns a normal array, as shown next. let total = fruits.count
let newArray = Array(repeating: "Cherry", count: total)
fruits.replaceSubrange(0..<total, with: newArray)
Listing 3-87: Casting arrays of type ArraySlice
print(fruits) // "["Cherry", "Cherry", "Cherry"]"


var fruits = ["Banana", "Orange", "Apple", "Cherry"]
var someFruits = fruits[0..<2] // ["Banana", "Orange"]
var newArray = Array(someFruits) In this example, we create an array with the same amount of elements as
 the fruits array and then use the replaceSubrange() method to replace every
element with a new one.
The Array structure also offers the removeSubrange() and replaceSubrange() The methods to remove and replace elements of an array are not selective
methods to remove and replace a range of elements. enough; they affect the elements in a specific index or a range of indexes
without considering their values. If we want to perform a more specific job,
Listing 3-88: Removing and replacing elements we can use the filter() method. This method takes a closure and sends each
 element to the closure for processing. If the closure returns true, the
var fruits = ["Banana", "Orange", "Apple", "Banana", "Banana"] element is included in the new array, otherwise it is ignored, as shown
fruits.removeSubrange(1...2)
fruits.replaceSubrange(0..<2, with: ["Cherry", "Cherry"])
next.
print(fruits) // "["Cherry", "Cherry", "Banana"]"

Listing 3-90: Filtering the values of an array

In Listing 3-88, we call the removeSubrange() method to remove the range of
var fruits = ["Apple", "Grape", "Banana", "Grape"]
elements from index 1 to 2 (getting an array filled with the value var filteredArray = fruits.filter({ $0 != "Grape" })
"Banana"), and then we call the replaceSubrange() method to replace the print(filteredArray) // "["Apple", "Banana"]"

elements from index 0 to 1 with another array filled with "Cherries". This is
just to illustrate how the methods work, but it shows a recurrent situation
The filter() method sends the values one by one to the closure, the closure
in app development where sometimes we need to fill a collection with
replaces the placeholder ($0) with the current value, compares it with the
elements of the same value. When working with arrays, this is easy to
value "Grape", and returns a Boolean with the result. If the value is true, the
element is included in filteredArray. Listing 3-93: Using a structure initializer with the map() method
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 let list = [1, 2, 3, 4, 5]
array one by one and returns another array with the results produced by let listtext = list.map(String.init)
print(listtext) // "["1", "2", "3", "4", "5"]"
the closure. 
Listing 3-91: Mapping an array 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
let list = [2, 4, 8, 16] the value to the initializer, the initializer returns a new String structure with
let half = list.map({ $0 / 2 }) that value, and the process continues as always.
print(half) // "[1, 2, 4, 8]"
 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
The example in Listing 3-91 defines a list of integers and then calls the map() an array, it sends the result back to the closure to get only one value in
method on the array to divide each value by 2. The map() method sends the return. For instance, the following code uses the reduce() method to get the
values of the array to the closure one by one, the closure replaces the result of the addition of all the numbers in an array.
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 Listing 3-94: Reducing an array
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 let list = [2, 4, 8, 16]
the closure. For instance, the following code converts the values into let total = list.reduce(0, { $0 + $1 })
print(total) // "30"
strings with the String() initializer. 
Listing 3-92: Converting the elements of an array into strings 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
let list = [1, 2, 3, 4, 5] closure. In the first cycle, the values sent to the closure are the ones
let listtext = list.map({ String($0) }) provided by the first argument (0) and the first value of the array (2). In the
print(listtext) // "["1", "2", "3", "4", "5"]"
 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).
When all we want to do is to initialize a new structure with the value The loop goes on until all the values of the array are processed.
received by the closure, instead of a closure, Swift allows us to provide the When it comes to sorting the elements of an array, there are several
structure's initializer. The value received by the closure is sent to the options available. The most frequently used are reversed() and sorted() (and its
initializer and a new structure of that type is returned. 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 When the sorted() method is executed, it performs a loop. On each cycle,
returned by the method is stored in a structure of type ReversedCollection. As two values of the fruits array are sent to the closure. The closure compares
we did before with the ArraySlice type, we can cast these values as Array the values and returns true or false accordingly. This indicates to the sorted()
structures with the Array() initializer. method which value should appear before the other in the new array,
effectively sorting the elements. Unlike the example we programmed for
Listing 3-95: Reversing the elements of an array
the filter() method, this one does not compare the argument against a

specific value. This allows us to order arrays of any data type. For example,
var fruits = ["Apple", "Blueberry", "Banana"]
var array = Array(fruits.reversed()) // ["Banana", "Blueberry", "Apple"] we can use the closure to sort an array of integers.

If we want to sort the elements in a custom order, we can use the sorted(by:) Listing 3-99: Sorting strings according to the number of characters
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 var fruits = ["Apple", "Blueberry", "Banana", "Grape"]
two elements and returns true if the first element should appear before the var newArray = fruits.sorted(by: { $0.count < $1.count })
second element, or false otherwise. print(newArray) // ["Apple", "Grape", "Banana", "Blueberry"]

Listing 3-97: Sorting the elements of an array in a custom order Arrays also include two powerful methods to compare elements: min() and

max(). These methods compare the values and return the smallest or
var fruits = ["Apple", "Raspberry", "Banana", "Grape"]
var newArray = fruits.sorted(by: { $0 > $1 }) largest, respectively.
print(newArray[0]) // "Raspberry"

Listing 3-100: Getting the largest element

let ages = [32, 540, 12, 27]
optional binding again to read the value and store it in the firstAge constant. if let index = ages.firstIndex(of: 540) {
The first and last properties only get the first and last values, respectively. To print("The value is at the position \(index)") // 1
}
search for any value in the array or its index, the Array structure offers the 
following methods.
If what we need instead is to get the index of a value that meets a
firstIndex(of: Element)—This method performs a search from the condition, we can use methods like firstIndex(where:) or lastIndex(where:)
beginning of the array and returns the index of the first element that depending on whether we want to search from the beginning or the end of
matches the value of the of argument. the array.
Listing 3-103: Getting the index of a value that meets a condition Set Structures


let ages = [32, 540, 12, 27, 54]
let first = ages.firstIndex(where: { $0 < 30 })
if first != nil { If we store two elements in an array, one element automatically receives
print("The first value is at index \(first!)") // 2
} the index 0 and the other the index 1. This correlation between indexes
 and values never changes, allowing elements to be listed always in the
right order and have elements with the same value at different indexes.
In this example, we look for the index of a value smaller than 30. The But if we don't care about the order, we can create a set. Sets are like
firstIndex(where:) method reads every value of the array from the beginning arrays, but they do not assign an index to their values, therefore there is no
and sends them to the closure assigned to the where argument. The closure order and all the values are unique. Sets are created from the Set structure.
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 Set<Type>()—This initializer returns an empty Set structure of the
false, otherwise it returns true and the index of that value is assigned to the data type indicated by the value of Type.
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. 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.
To access and process the elements of a set, we can use a for in loop, as union(Collection)—This method returns a new set created with the
we did before with strings and arrays, but sets also provide their own values of the original set plus the values provided by the argument
properties and methods for this purpose. (an array or another set).
subtract(Collection)—This method returns a new set created by
count—This property returns the number of elements in the set. subtracting the elements provided by the argument to the original
isEmpty—This property returns a Boolean value that indicates set.
whether the set is empty or not.
intersection(Collection)—This method returns a new set created
contains(Element)—This method returns a Boolean value that with the values of the original set that match the values provided by
indicates whether there is an element in the set with the value the argument (an array or another set).
specified by the argument.
remove(Element)—This method removes from the set the element
contains(where: Closure)—This method returns a Boolean value with the value provided by the argument.
that determines if the set contains an element that meets the
isSubset(of: Set)—This method returns a Boolean value that
condition in the closure.
indicates whether or not the set is a subset of the set specified by the
min()—This method compares the elements in the set and returns of argument.
the smallest.
isSuperset(of: Set)—This method returns a Boolean value that
max()—This method compares the elements in the set and returns indicates whether or not the set is a superset of the set specified by
the largest. the of argument.
sorted()—This method returns an array with the elements of the set isDisjoint(with: Set)—This method returns a Boolean value that
in ascending order. indicates whether or not the original set and the set specified by the
sorted(by: Closure)—This method returns an array with the with argument have elements in common.
elements of the set in the order determined by the closure specified
by the by argument. Using these methods, we can easily access and modify the values of a
randomElement()—This method randomly selects an element from set. For instance, we can implement the contains() method to search for a
value.
the set and returns it. If the set is empty, the value returned is nil.
shuffled()—This method returns an array with the elements of the Listing 3-106: Using contains() to find an element in a set
set in random order. 
insert(Element)—This method inserts a new element in the set with var fruits: Set = ["Apple", "Orange", "Banana"]
the value provided by the argument. if fruits.contains("Apple") {
print("Apple exists!")
} 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
To insert a new element, we just have to execute the insert() method. does not perform any action.
To remove an element, we must call the remove() method.
Listing 3-107: Inserting a new element in a set

Listing 3-108: Removing an element from a set
var fruits: Set = ["Apple", "Orange", "Banana"]

if !fruits.contains("Grape") { var fruits: Set = ["Apple", "Orange", "Banana"]
fruits.insert("Grape")
} if let removed = fruits.remove("Banana") {
print("The set has \(fruits.count) elements") // 4 print("\(removed) was removed") // "Banana was removed"
 }

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.
Dictionary Structures
The rest of the methods available are straightforward. The following

example joins two sets with the union() method and then subtracts
elements from the result with subtract().
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
Listing 3-110: Combining sets
dictionaries, we can define the indexes ourselves using any custom value

var fruits: Set = ["Apple", "Banana"]
we want. Each index, also known as key, must be explicitly declared along
var newSet = fruits.union(["Grapes"]) // "Banana", "Grapes", "Apple" with its value. Swift offers multiple syntaxes to create a dictionary,
newSet.subtract(["Apple", "Banana"]) // "Grapes"
including the following initializers.

Listing 3-111: Comparing sets 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
var store: Set = ["Banana", "Apple", "Orange", "Pear"] brackets with a colon, as in var list: [String: String] = [:]. The latest is also used
var basket: Set = ["Apple", "Orange"]
to define a dictionary with initial values. In this case, the keys and values
if basket.isSubset(of: store) { are separated by a colon and the items are separated by comma, as in the
print("The fruits in the basket are from the store")
} 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 Dictionaries return optional values. If we try to read an element with a key
course, if the keys and the values are of a clear data type, Swift can infer that does not exist, the value returned is nil.
them.
Listing 3-116: Reading an element that does not exist
Listing 3-113: Declaring a dictionary with type inference 

var list = ["First": "Apple", "Second": "Orange"]
var list = ["First": "Apple", "Second": "Orange"] print(list["Third"]) // nil


The code in Listing 3-116 tries to read a value with the "Third" key in the list
As with arrays, if we want to read or replace a value, we must declare the
key (index) in square brackets after the name of the dictionary. 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
Listing 3-114: Assigning a new value to an element of a dictionary unwrap it.

var list = ["First": "Apple", "Second": "Orange"] Listing 3-117: Reading a value in a dictionary
list["Second"] = "Banana" 

var list = ["First": "Apple", "Second": "Orange"]
if let first = list["First"], let second = list["Second"] {
The second statement in Listing 3-114 assigns a new value to the element print("We have \(first) and \(second)") // "We have Apple and Orange"
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
Since dictionary elements are optionals, we can assign the value nil to
exist, a new element is created, as shown next.
remove them. The following example removes the element with the "First"
Listing 3-115: Adding a new element to a dictionary key.

var list = ["First": "Apple", "Second": "Orange"]
Listing 3-118: Removing an element from a dictionary
list["Third"] = "Banana" 
print(list) // "["Second": "Orange", "First": "Apple", "Third": "Banana"]"
var list = ["First": "Apple", "Second": "Orange"]

list["First"] = nil

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 As with arrays and sets, we can also iterate over the values of a dictionary
with the specified key and value. with a for in loop. The value produced by each cycle of the loop is a tuple
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.
Listing 3-128: Declaring the enumeration values in one statement In the last statement of Listing 3-130, we assign a new value to mynumber.
 The value .two may have been written as Number.two. Both syntaxes are valid,
enum Number { but Swift infers that the new value is of the same data type, so it is not
case one, two, three
} necessary to declare the name anymore.
 Like Booleans, enumeration types may be used as signals to indicate a state
that can be checked later to decide whether to perform a certain task.
An enumeration is a custom data type. As we did with structures, we must Therefore, they are frequently used with conditionals and loops. The
create a variable of this type and assign to that variable one of the possible following example checks the value of an enumeration variable with a switch
values using dot notation. statement. This statement is particularly useful when working with
enumerations because these data types have a limited set of values, Raw Values
making it easy to define a case for each one of them.

Listing 3-131: Using switch with an enumeration type
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
enum Number {
case one from 0, but we can assign our own.
case two
case three
}
Listing 3-132: Assigning raw values to enumeration values
var mynumber = Number.two 
enum Number: String {
switch mynumber {
case one = "Number One"
case .one:
case two = "Number Two"
print("The number is 1") case three = "Number Three"
case .two:
}
print("The number is 2") // "The number is 2"
var mynumber = Number.one
case .three: 
print("The number is 3")
}
 Enumerations behave like structures. We can define our own properties
and methods inside an enumeration, and they also include initializers,
In this example, the Number enumeration is defined and then the properties, and methods by default. The most useful property is called
mynumber variable is declared with the value two. Next, a switch statement rawValue, which lets us read the raw value of each case.
compares the value of this variable with the three possible values of its
type and prints a message on the console. Listing 3-133: Reading raw values

enum Number: String {
case one = "Number One"
case two = "Number Two"
case three = "Number Three"
}
var mynumber = Number.one
print("The value is \(mynumber.rawValue)") // "The value is Number One"

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.
Associated Values parenthesis, similar to what we did with tuples before (see Listing 2-37).
If we need to check a single case, we can use an if or a guard statement and

assign the value to the case we want to check.
Objects are data types that encapsulate data and functionality in the form Like structures and enumerations, objects are defined first and then
of properties and methods, but unlike the structures and enumerations instances are created from their definition. The definitions of objects are
introduced before they are stored by reference, which means that more called Classes, and what we called objects are the instances created from
than one variable can reference the same object in memory. 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.
In Listing 3-139, the Employee() initializer creates a new instance of the class Type Properties and Methods
Employee. The words instance and object are synonyms, so we can say that

in this example we have created a new object called employee1 containing
two properties, name and age. We have studied type properties and methods before with structures.
Of course, we can also modify the values of the properties of an object These are properties and methods accessible from the data type, not from
from its methods, but unlike structures, these methods can modify the
the instances. They work in classes the same way as in structures, but
properties of their own object without adding anything to the definition
instead of the static keyword we must use the class keyword to define them.
(they do not need to be declared as mutating).
Listing 3-141: Declaring a type method for a class
Listing 3-140: Modifying properties from the object’s methods


class Employee {
class Employee { var name = "Undefined"
var name = "Undefined" var age = 0
var age = 0
class func description() {
func changename(newname: String, newage: Int) { print("This class stores the name and age of an employee")
name = newname }
age = newage }
} Employee.description()
} 
let employee1 = Employee()
employee1.changename(newname: "Martin", newage: 32)
print("Name: \(employee1.name)") // "Name: Martin" 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
In Listing 3-140, the changename() method is added to the Employee class to
printed on the console. Again, we don't have to create an instance because
modify the values of the properties. After the instance is created, we call
the method is executed on the class itself.
this method to assign the values "Martin" and 32 to the name and age
properties, respectively.
IMPORTANT: Classes can also use the static keyword to define type
properties and methods. The difference between the static and class
IMPORTANT: As well as structures, we can create all the objects we
keywords is that properties and methods defined with the static
need from the same definition (class). Each object will have its own
keyword are immutable and those defined with the class keyword can
properties, methods, and values.
be modified by subclasses. (We will learn about subclasses and
inheritance later in this chapter.)
Reference Types property is reflected in the other because both variables point to the same
object in memory (they reference the same instance).

Figure 3-2: Objects stored in memory
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.
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 Memory Management
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
Because objects are stored by reference, they can be referenced by several
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. variables at the same time. If a variable is erased, the object that the
Another useful application of the self keyword is to reference the data type variable is referencing cannot be erased from memory because another
itself. The value generated by reading the self keyword on a data type is variable could still be using it. This creates a situation in which the device's
called Metatype. A metatype refers to the type itself, not an instance of it. memory is filled with objects that are no longer necessary. The solution
For example, the value Int.self refers to the definition of the Int data type, provided by Apple is an automatic system that counts the number of
not an integer number created from that type, as shown in the following variables referencing an object and only removes the object from memory
example. when all the references are erased (all the variables were erased, set to nil,
or they were assigned a reference to another object). The system is called
Listing 3-143: Referring to the data type with self ARC (Automatic Reference Counting). ARC automatically erases the objects

when there is no longer a constant or a variable containing a reference to
let reference = Int.self
let newnumber = reference.init(20) that space in memory.
print(newnumber) // "20" In an ideal scenario, this system works like magic, counting how many

references we create to the same object and erasing that object when
none of those references exist anymore. But there are situations in which
The code in Listing 3-143 stores a reference to the Int data type in a
we can create something called Strong Reference Cycle. This happens when
constant and then uses that constant to create an instance of Int with the
two objects have a property that references the other object.
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
Listing 3-144: Referencing one object from another
references of data types to methods and initializers of other types, as we

will see in further chapters.
class Employee {
var name: String?
var location: Department?
}
class Department {
var area: String?
var person: Employee?
}
var employee: Employee? = Employee()
var department: Department? = Department()
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 In this example, we assume that the value nil was assigned to the employee
the employee and department variables. The reference in the department variable and department variables, and in consequence the objects are not accessible
is assigned to the location property of the employee object, and the reference anymore, but they are preserved in memory because ARC has no way to
in the employee variable is assigned to the person property of the department know that they are no longer required.
object. After this, each object contains a reference to the other. Swift solves this problem by classifying the references into three
categories: strong, weak, and unowned. Normal references are strong;
Figure 3-3: Objects referencing each other 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.

Listing 3-145: Assigning weak references
At this point, each object is referenced by a variable and a property. The 
object of the Employee class is referenced by the employee variable and the class Employee {
person property, and the object of the Department class is referenced by the var name: String?
var location: Department?
department variable and the location property. If, for some reason, we do not }
need to access these objects from our code anymore and erase or modify class Department {
var area: String?
the values of the employee and department variables, ARC will not erase the weak var person: Employee?
objects from memory because their properties still have a reference that }
keeps them alive. var employee: Employee? = Employee()
var department: Department? = Department()
department?.area = "Mail"
department?.person = employee Inheritance


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 One of the main purposes of structures and objects is to define pieces of
from the Employee class is erased from memory because the only reference code that can be copied and shared. The code is defined once and then
left is the weak reference from the person property. After this object instances (copies) of that code are created every time they are required.
disappears, the object created from the Department class does not have any This programming pattern works well when we define our own code but
other strong reference either, so it is also erased from memory. presents some limitations when working with code programmed by other
The unowned reference works the same way, but it differs from the weak developers and shared through libraries and frameworks. The
reference on the type of values it applies to. Weak references apply to programmers creating the code for us cannot anticipate how we are going
variables with optional values (they can be empty at some point) and to use it and all the possible variations required for every application. To
unowned references apply to non-optional values (they always have a provide a solution to this problem, classes incorporate inheritance. A class
value). can inherit properties and methods from another class and then improve it
by adding properties and methods of its own. This way, programmers can
IMPORTANT: Closures can create strong reference cycles if we try to share classes and developers can adapt them to their needs.
To illustrate how inheritance works, the following examples present a
access properties or methods defined outside the closure. If we
situation in which a class must be expanded to contain additional
need to reference properties or methods with self inside a closure,
information that was not initially contemplated.
we can declare the reference to self as weak with the syntax [weak self]
or [unowned self]. The expression must be declared before the closure's
Listing 3-146: Defining a basic class
parameters. For more information on ARC and how to avoid strong

reference cycles, visit our website and follow the links for this
class Employee {
chapter. var name = "Undefined"
var age = 0
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 class Employee {
var name = "Undefined"
require a badge that also displays the department they work in. One option var age = 0
is to define another class with the same properties and methods and add
func createbadge() -> String {
what we need, but this produces redundant code, and it is difficult to do return "Employee \(name) \(age)"
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
class OfficeEmployee: Employee {
that inherits the characteristics of the basic class and adds its own var department = "Undefined"
properties and methods to satisfy the new requirements. }
let employee = OfficeEmployee()
To indicate that a class inherits from another class, we must write the employee.name = "George"
name of the basic class after the name of the new class separated by a employee.age = 25
employee.department = "Mail"
colon.
var badge = employee.createbadge()
print("Badge: \(badge)") // "Badge: Employee George 25"
Listing 3-147: Inheriting properties and methods from another class 

class Employee { A class like Employee is called Superclass, and a class that inherits from
var name = "Undefined"
var age = 0
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
func createbadge() -> String { methods of the Employee superclass. A class can inherit from a superclass
return "Employee \(name) \(age)"
} 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
class OfficeEmployee: Employee {
var department = "Undefined" 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
The OfficeEmployee class added to our code in Listing 3-147 only has one
example, considering the code in Listing 3-148, objects created from
property called department, but it inherits the name and age properties, and the class OfficeEmployee have access to the department property of this
also the createbadge() method from the Employee class. All these properties class and the properties and methods of the Employee class, but
and methods are available in any of the objects created from the objects created from the Employee class do not have access to the
OfficeEmployee class, as shown next. department property.
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
In Listing 3-152, we create the list array again with objects from the
store objects of the subclasses in it, as we did in Listing 3-151 with the list
same classes defined in the previous example, but this time we add a for in
array.
loop to iterate over the array and count how many objects of each class we
This is all good until we try to read the array. The array was declared of
have found. The if statement inside the loop implements the is operator to
type Employee, so we can only access the properties defined in the Employee
check if the current object stored in the obj constant is of type OfficeEmployee
class. Also, there is no way to know what type of object each element is.
or WarehouseEmployee and increments the counter respectively (countOffice or
We could have an OfficeEmployee object at index 0 and later replace it with a
countWarehouse).
WarehouseEmployee object. The indexes do not provide any information to
Counting objects is not really what these operators are all about. The
identify the objects. Swift solves these problems with the is and as
idea is to figure out the type with the is operator and then convert the
operators.
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
is—This operator returns a Boolean value indicating whether the
conversions are not always guaranteed, and that is why this operator
value is of a certain data type.
comes in two more forms: as! and as?. These versions of the as operator
as—This operator converts a value of one class to another class when work like optionals. The as! operator forces the conversion and returns an
possible. 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
Identifying an object is easy with the is operator. This operator returns failure.
a Boolean value that we can use in an if statement to check the object’s
class. Listing 3-153: Casting an object

Listing 3-152: Identifying the object’s data type for obj in list {
 if obj is OfficeEmployee {
let temp = obj as! OfficeEmployee
var countOffice = 0 temp.deskNumber = 100
var countWarehouse = 0 } else if obj is WarehouseEmployee {
let temp = obj as! WarehouseEmployee
for obj in list { temp.area = "New Area"
if obj is OfficeEmployee { }
countOffice += 1 }
} else if obj is WarehouseEmployee { 
countWarehouse += 1
}
} When we use the as! operator we are forcing the conversion, so we
print("We have \(countOffice) employees working at the office") // 2
print("We have \(countWarehouse) employees working at the warehouse") //1
need to make sure that the conversion is possible or otherwise the app will
crash. (This is the same that happens when we unwrap optionals with the print("The area of employee 1 is \(myarea)") // "Undefined"

exclamation mark.) In the code of Listing 3-153, we only use this operator
after we have already checked with the is operator that the object is of the In this example, we do not assign the object to any variable; we just cast
right class. Once the object is casted (converted) into its original data type, the element of the list array at index 1 as a WarehouseEmployee object inside
we can access its properties and methods. In this example, the objects the parentheses and then access its area property. The value of this
returned by the as! operator are stored in the temp constant and then new property is stored in the myarea constant and then printed on the console.
values are assigned to the deskNumber and area properties. All we need to remember is that conversions performed with the as!
Checking for the type before casting is redundant. To simplify the operator are only possible when we are sure it is going to be successful.
code, we can use the as? operator. Instead of forcing the conversion and
crashing the app, this version of the as operator tries to perform the IMPORTANT: The as! operator is applied when the conversion is
conversion and returns an optional with the result. guaranteed to be successful, and the as? operator is used when we
are not sure about the result. But we can also use the basic as
Listing 3-154: Casting an object with the as? operator operator when the Swift compiler can verify that the conversion will
 be successful, as when we are casting some primitive data types
for obj in list { (e.g., String values into NSString objects).
if let temp = obj as? OfficeEmployee {
temp.deskNumber = 100
} else if let temp = obj as? WarehouseEmployee { The as operator works on objects that belong to the same class
temp.area = "New Area" hierarchy. Because sometimes the objects that require casting are not in
}
} the same hierarchy, Swift defines several generic data types to represent
 values of any kind. The most frequently used are Any (structures), AnyObject
(objects), and AnyClass (classes). By taking advantage of these generic types,
In this example, we use optional binding to cast the object and assign we can create collections with values that are not associated with each
the result to the temp constant. First, we try to cast obj as an OfficeEmployee other.
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 Listing 3-156: Working with objects of AnyObject type
the WarehouseEmployee class and modify its area property. 
Casting can also be performed on the fly if we are sure that the class Employee {
conversion is possible. The statement to cast the object is the same but it var name = "Undefined"
must be declared between parentheses. }
class Department {
var area = "Undefined"
Listing 3-155: Casting an object on the fly }
 var list: [AnyObject] = [Employee(), Department(), Department()]
properties are modified following the same procedure used in previous class Employee {
var name: String
examples. var age: Int
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.
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 private—This keyword determines that an entity is accessible only
this time a closure is assigned to the name property to get the employee's from the context in which it was created (e.g., a private property in a
name from a server (we will see how to retrieve information from the Web class will only be accessible from methods of the same class).
in Chapter 17). Because we declared this property as lazy, the closure will fileprivate—This keyword determines that an entity is accessible
only be executed when we try to read it. If we execute the example as it is, only from the file in which it was declared (e.g., a fileprivate property in
we get nothing in return, but if we read the name property with a statement a class will only be accessible by other entities defined inside the file
at the end, we will see the text "Loading..." printed on the console. in which it was declared).
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 As we will see later, most of these keywords apply to frameworks and are
source files, but it also applies to single properties and methods. Source
rarely used in single applications. By default, the properties and methods
files are the files we create for our application, the properties and methods
we include in our classes and structures are declared internal, which means
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. that our classes and structures are only available from inside our
For instance, a single application and each of the frameworks included in it application (module). Unless we are creating our own frameworks, this is
are considered modules (we will introduce frameworks in Chapter 4). all we need for our applications, and is the reason why we didn't have to
Considering this classification, Swift defines five keywords to determine specify any keyword when we defined our structures and classes before.
accessibility. 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
open—This keyword determines that an entity is accessible from the or avoid modifying values by mistake, we can declare some of them as
module it belongs to and other modules. private, as shown next.
public—This keyword determines that an entity is accessible from
the module it belongs to and other modules. The difference between Listing 3-163: Declaring private properties
public and open is that we can't create subclasses of public classes 
outside the module in which they were defined. This also applies to class Employee {
private var name = "Undefined"
methods and properties (e.g., public methods can't be overridden private var age = 0
outside the module in which they were declared). func showValues() {
internal—This keyword determines that an entity is accessible only print("Name: \(name)")
print("Age: \(age)")
inside the module in which it was created. This is the default access }
}
mode for applications. By default, every entity defined in our
let employee = Employee()
application is only accessible from inside the application. employee.showValues()

Singletons
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
As already mentioned, we can create as many objects as we want from a
in the class. If we try to read their values from outside the object using dot
single class. These objets are independent from one another; they have
notation, Xcode will return an error (employee.name).
their own properties and methods and process their own values. But
If what we want is to be able to read the property from outside the object
sometimes our code needs only one object to be created from a class, so
but not allow assigning new values to it, we can declare it with a public
getter but a private setter. every piece of code will have access to the same object and working on the
same values. To guarantee that only one instance of a class is created and
Listing 3-164: Declaring a public getter and private setter the instance is available from anywhere in our code, the class needs to

implement two things: a type property to create the only instance
available, and a private initializer that doesn't allow the code to create
class Employee {
private var name = "Undefined" more, as shown in the following example.
public private(set) var age = 0
The age property in the Employee class of Listing 3-164 was declared as private init(newName: String, newAge: Int) {
name = newName
public, so everyone can read its value, but with a private setter (private(set)), age = newAge
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 let employee1 = Employee.shared
calls the method, but this time we can read the value of the age property let employee2 = Employee.shared
employee1.name = "George"
and print it on the console because it was declared with a public getter.
print("\(employee1.name) - \(employee2.name)") // "George - George"

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 3.6 Protocols
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 main characteristics of classes, and therefore objects, are the capacity
the employee2 object. to encapsulate data and functionality and the possibility to share and
Because the purpose of this class is to only allow the creation of one improve code through inheritance. This introduced an advantage over
instance (one object), we prefix the initializer with the private keyword. previous paradigms and turned the Object-Oriented Programming
As a result, only the single instance created by the shared property is paradigm into the industry standard for a while. But that changed with the
available. That is the reason why the object created from this class is introduction of protocols in Swift. Protocols define properties and methods
called Singleton. 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 Definition of Protocols
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
Protocols are defined with the protocol keyword followed by the name and
know that a structure conforms to a protocol, we can always be sure that
the list of properties and methods between braces. No values or
besides its own definitions, it will also include the properties and methods
defined by the protocol. In addition, protocols can be extended to provide statements are assigned or declared inside a protocol, only the names and
their own implementations of the properties and methods we want the the corresponding data types. Because of this, methods are defined as
structures to have in common, allowing the paradigm to completely always, but they omit the braces and the statements, and properties must
replace classes and objects. 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
IMPORTANT: The Swift paradigm is built from the combination of Listing 3-43 for an example of getters and setters). To indicate that the
structures and protocols, but protocols may also be adopted by structure conforms to the protocol, we must include the protocol’s name
enumerations and classes. For instance, many frameworks use after the structure’s name separated by a colon, as shown in the following
protocols to offer a programming pattern called Delegation. We will example.
study how classes conform to protocols and how to implement
delegation later. Listing 3-166: Defining protocols

protocol Printer {
var name: String { get set }
func printdescription()
}
struct Employees: Printer {
var name: String
var age: Int
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() the printdescription() method. The common functionality defined by the
this protocol, and along with the protocol’s property and method it also protocol ensures that no matter what type of structure we are working
implements its own property called age. Although this property was not with, it will always have an implementation of printdescription().
defined in the protocol, we can read it inside the printdescription() method Protocols are also data types. This allows us to associate structures by the
and print its value. common functionality.
The advantage of this practice is evident when structures of different types
conform to the same protocol, as shown in the following example. Listing 3-168: Using protocols as data types

Listing 3-167: Defining multiple structures that conform to the same let employee1 = Employees(name: "John", age: 32)
let office1 = Offices(name: "Mail", employees: 2)
protocol
 var list: [Printer] = [employee1, office1]
for element in list {
protocol Printer {
element.printdescription()
var name: String { get set }
}
func printdescription()

}
struct Employees: Printer {
var name: String Listing 3-168 uses the same protocol and structures defined in the previous
var age: Int
example, but this time it stores the instances in an array. The type of the
func printdescription() { array was defined as Printer, which means the array may contain structures
print("Description: \(name) \(age)")
} 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
struct Offices: Printer {
var name: String always have an implementation of the name property and the printdescription()
var employees: Int
method.
func printdescription() { When we process a structure or an object as a protocol type, we can only
print("Description: \(name) \(employees)") // "Description: Mail 2"
}
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
let employee1 = Employees(name: "John", age: 32)
let office1 = Offices(name: "Mail", employees: 2)
the as operator as we did with classes before. The following example prints
office1.printdescription() the value of the age property if the element of the array is of type Employees.

Generic Protocols
var list: [Printer] = [employee1, office1]
for element in list { 
if let employee = element as? Employees {
print(employee.age) // "32"
} Protocols can also define generic properties and methods, but they work
element.printdescription()
} 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.
Because protocols are data types, we can use them to define variables, or
return them from functions. The following example declares a function Listing 3-171: Defining generic protocols
that returns a value of type Printer. 
protocol Printer {
Listing 3-170: Returning values of a protocol type associatedtype protype
var name: protype { get set }
 }
func getFile(type: Int) -> Printer { struct Employees: Printer {
var data: Printer! var name: String
if type == 1 { }
data = Employees(name: "John", age: 32) let employee = Employees(name: "John")
} else if type == 2 { print(employee.name) // "John"
data = Offices(name: "Mail", employees: 2) 
}
return data
}
This example defines a generic protocol called Printer and a structure
let file = getFile(type: 1) that conforms to that protocol called Employees. The protocol defines a
file.printdescription() // "Description: John 32"

generic type with the name protype and then declares a property of that
type. The property's real data type is defined by the structure or the class
The getFile() function in Listing 3-170 creates an instance of a structure that conforms to the protocol. In this case, the Employees structure defines
depending on the value received. If the type parameter is equal to 1, it the name property as type String, and that's the type of values we can use in
returns an instance of Employees, but if the value is equal to 2, it returns an the instances of this structure, but we could have declared the property as
instance of Offices. But because the value returned by the function is of type type Int or any other necessary.
Printer we know it will always include the printdescription() method.
Swift Protocols whether the values are equal or not). Swift primitive data types conform to
the Equatable protocol and implement its methods, but we can also

implement them in our own data types to compare their values. For this
The Swift language makes use of protocols extensively. Almost every API purpose, we must declare that the data type conforms to the protocol and
includes protocols that define common features and behavior. But there implement the methods required by it. The Equatable protocol requires only
are also important protocols defined in the Swift Standard Library that we one method called == to check for equality. (The system infers that if two
can use to improve our custom data types. The following are the most values are not equal, they are different, and therefore the method for the
frequently used. != operator is optional.) This method must have a name equal to the
operator (==), receive the two values to compare, and return a Boolean
Equatable—This protocol defines a data type which values can be value to communicate the result. For instance, we can make our Employees
compared with other values of the same type using the operators == structure conform to the Equatable protocol and implement a method with
and !=. the == name to be able to compare two different instances of the same
Comparable—This protocol defines a data type which values can be structure.
compared with other values of the same type using the operators >, <,
>=, and <=.
Listing 3-172: Conforming to the Equatable protocol

Numeric—This protocol defines a data type that only works with struct Employees: Equatable {
values that can participate in arithmetic operations. var name: String
var age: Int
Hashable—This protocol defines a data type that provides the hash
value (unique identifier) required for the value to be included in static func == (value1: Employees, value2: Employees) -> Bool {
return value1.age == value2.age
collections, such as sets and dictionaries. }
}
CaseIterable—This protocol defines a data type, usually an let employee1 = Employees(name: "John", age: 32)
enumeration without associated values, that includes a property let employee2 = Employees(name: "George", age: 32)
called allCases that contains the collection of all the cases included in let message = employee1 == employee2 ? "Equal" : "Different"
the data type. print(message) // "Equal"

These protocols are responsible of elemental processes performed by In this example, we use the == method to compare the values of the
the system and the Swift language. For example, when we compare two ageproperties and therefore the structures are going to be equal when the
values with the == or != operators, the system checks whether the values employees are the same age. In this case, both instances are created with
conform to the Equatable protocol and then calls a type method in the data the value 32 and therefore the value "Equal" is assigned to the message
type to compare them and solve the condition (true or false, depending on constant when we compare the objects with the ternary operator.
If what we want is to compare each of the properties in the structure, Equatableprotocol, and therefore we can compare the values inside the
then we can omit the method. When we conform to the Equatable protocol, function.
the compiler automatically generates the method for us to compare all the
values of the structure (in this case, name and age). Listing 3-174: Adding a type constraint to a generic function

Listing 3-173: Letting the compiler create the protocol methods for us struct Employees: Equatable {
var name: String

var age: Int
struct Employees: Equatable { }
var name: String func compareValues<T: Equatable>(value1: T, value2: T) -> String {
var age: Int let message = value1 == value2 ? "equal" : "different"
} return message
let employee1 = Employees(name: "John", age: 32) }
let employee2 = Employees(name: "George", age: 32) let employee1 = Employees(name: "George", age: 55)
let employee2 = Employees(name: "Robert", age: 55)
let message = employee1 == employee2 ? "Equal" : "Different"
print(message) // "Different" let result = compareValues(value1: employee1, value2: employee2)
 print("The values are \(result)") // "The values are different"

Because we did not declare the == method in the example of Listing 3-
173, the system creates the method for us and compares the values of all The conformance to the protocol is specified inside the angle brackets
the properties. As a result, the system determines that the objects are after the name of the generic type. The compareValues() function in Listing 3-
different (the ages are the same, but the names are not). 174 declares the T type to conform to Equatable and then compares the
Of course, we could have compared the properties directly values with a ternary operator and returns the result. In this case, the ages
(employee1.name == employee2.name) but being able to compare the objects of the employees are the same (55), but the names are different ("George"
instead simplifies the code and allows us to use our structures (or objects) and "Robert"), and therefore the system considers the structures to be
in APIs that require the values to be comparable. For example, when we different.
created a generic function earlier in this chapter, we could not perform any Another protocol used as a type constraint is Numeric. This protocol
operations on the values (see Listing 3-15). Because the data type used in determines that the data types of the values received by the function must
those functions is generic, Swift is incapable of knowing the capabilities of support arithmetic operations.
the data type and therefore Xcode returns an error if we try to perform
operations on the values, but we can easily solve this problem by making Listing 3-175: Using the Numeric protocol to set a type constraint

the generic type conform to a protocol. This feature is called Type
func calculateResult<T: Numeric>(value1: T, value2: T) {
Constraint because it constrains the generic type to a data type with print(value1 + value2) // 7.5
certain capabilities. For instance, the function in the following example }
calculateResult(value1: 3.5, value2: 4)
receives two generic values, but only of a data type that conforms to the 
The calculateResult() function in Listing 3-175 is a generic function and When we compare two instances of the Employees structure, the system
therefore it can receive any value of any type, but because we set a type calls the corresponding type method and the method returns true or false
constraint with the Numeric protocol, the function can only receive values of according to the values of the age property. Because in this example the
data types that can participate in arithmetic operations. value of age in the employee1 structure is not greater than the value of age in
Besides comparing for equality with the Equatable protocol, we can also the employee2 structure, we get the message "Robert is older".
compare magnitudes with the Comparable protocol. This protocol is like Another useful protocol is Hashable. Every time we include a structure
Equatable, but the system does not offer a default implementation of the or an object in a set or use them as the index of a dictionary, the system
type methods, we must implement them ourselves. The protocol requires requires the data type to provide a hash value that can be used to uniquely
four methods to represent the operations >, <, >= and <=. In the following identify each element. This is a random integer that is created based on
example, we compare the ages of the employees. the values of the properties. The purpose of the Hashable protocol is to
define properties and methods to handle this value. Most of the data types
Listing 3-176: Conforming to the Comparable protocol defined by Swift conform to this protocol and that is why we do not have
 any problems when including these values in a set or as the index of
struct Employees: Comparable { dictionaries, but for custom structures and objects we must provide the
var name: String
var age: Int hash value ourselves. Fortunately, if the values in our data type are already
Hashable, we do not need a specific property to be used to create the hash
static func > (value1: Employees, value2: Employees) -> Bool {
return value1.age > value2.age value, all we need to do is conform to the protocol and the system creates
} the value for us. The following example makes the Employees structure
static func < (value1: Employees, value2: Employees) -> Bool {
return value1.age < value2.age
conform to the Hashable protocol, so we can include the instances in a set.
}
static func >= (value1: Employees, value2: Employees) -> Bool { Listing 3-177: Conforming to the Hashable protocol
return value1.age >= value2.age
} 
static func <= (value1: Employees, value2: Employees) -> Bool { struct Employees: Hashable {
return value1.age <= value2.age var name: String
} var age: Int
} }
let employee1 = Employees(name: "George", age: 32) let employee1 = Employees(name: "John", age: 32)
let employee2 = Employees(name: "Robert", age: 55) let employee2 = Employees(name: "Robert", age: 55)
At the end of Listing 3-178, we print the value of the hashValue property.
Hash values are random integers created based on the values of the Because the resulting value is always an integer calculated randomly every
properties. If we just conform to the protocol, the system uses the values time the app is executed, we won't notice any difference, but this
of all the properties in the instance to create it (all the properties must be procedure may be useful when we manage sensitive information.
hashable), but we can specify which properties should be included by
The last protocol from our list is called CaseIterable. This is a simple
implementing the properties and methods defined by the protocol.
protocol that defines a property called allCases to store a collection with all
the cases in an enumeration. Again, the system automatically initializes this
hashValue—This property returns the instance's hash value. It is of
property, so all we need to do is to declare that the enumeration conforms
type Int.
to the protocol. In the following example, we define an enumeration with
hash(into: inout Hasher)—This method defines the properties that three cases and then iterate through the collection in the allCases property
are going to be included in the hasher to create the hash value. to print the names.
To calculate the hash value, the Swift Standard Library includes a Listing 3-179: Conforming to the CaseIterable protocol
structure called Hasher. This is the structure received by the hash(into:) 
method and it contains a method called combine() to tell the hasher which enum Departments: CaseIterable {
properties should be used to create the value. The following example case mail
case marketing
illustrates how to implement the hash(into:) method and call the combine() case managing
method on the hasher to create a hash value from the value of the name }
var message = ""
property. for department in Departments.allCases {
message += "\(department) "
}
Listing 3-178: Defining our own hash value print(message) // "mail marketing managing "
 
struct Employees: Hashable {
var name: String
var age: Int
Of course, we can also extend our own data types if we consider it In this example, we define a generic structure called Employees with a
appropriate. The following example extends our Employees structure to add generic property called value and then define an extension for this structure
a new method. with a method called doubleValue(), but this method will only be added to the
instance if the data type used to create the instance is Int. At the end, we
Listing 3-182: Extending custom data types create an instance with the value 25 and call the method, which multiplies
 the value by 2 and prints a string with the result. This works because we
struct Employees { created the instance with an integer, but if we try to use another type of
var name: String
var age: Int value, Xcode will show an error.
}
extension Employees {
func printbadge() {
print("Name: \(name) Age: \(age)")
}
}
let employee = Employees(name: "John", age: 50)
employee.printbadge() // "Name: John Age: 50"

Delegates property stores the instance of the Salary structure in charge of printing
 that data. The code creates the Salary instance first and then uses this value
to create the Employees instance. When we call the generatereport() method on
As we have already seen, an instance of a structure or an object can be the employee1 structure at the end, the method calls the showmoney() method
assigned to the property of another instance. For example, we could have on delegate, effectively delegating the task of printing the data to this
an instance of a structure called Employees with a property that contains an structure.
instance of a structure called Offices to store information about the office This pattern presents two problems. First, the structure that is delegating
where the employee works. This opens the door to new programming needs to know the data type of the structure that is going to become the
patterns where the instances adopt different roles. The most useful pattern delegate (in our example, the delegate property always has to be of type
is called Delegation. A structure or object delegates responsibility for the Salary). Following this approach, not every structure can be a delegate, only
execution of certain tasks to another structure or object. the ones specified in the definition. The second problem is related to how
we know which are the properties and methods that the delegate must
Listing 3-185: Delegating tasks implement. If the structure is too complex or is taken from a library, we
 could forget to implement some methods or properties and get an error
struct Salary { when the structure tries to access them. Both problems are solved by
func showMoney(name: String, money: Double) {
print("The salary of \(name) is \(money)")
protocols. Instead of declaring a specific structure as the delegate, we
} define a protocol and declare the delegate property to be of that type, as
}
struct Employees {
shown in the following example.
var name: String
var money: Double
Listing 3-186: Delegating with protocols
var delegate: Salary 
protocol SalaryProtocol {
func generatereport() { func showMoney(name: String, money: Double)
delegate.showMoney(name: name, money: money) }
} struct Salary: SalaryProtocol {
} func showMoney(name: String, money: Double) {
let salary = Salary() print("The salary of \(name) is \(money)")
var employee1 = Employees(name: "John", money: 45000, delegate: salary) }
}
employee1.generatereport() // "The salary of John is 45000.0" struct Employees {
 var name: String
var money: Double
The Employees structure in Listing 3-185 contains three properties. The
var delegate: SalaryProtocol
properties name and money store the employee’s data, but the delegate
func generatereport() {
delegate.showMoney(name: name, money: money) var money: Double
} var delegate: SalaryProtocol
}
let salary = Salary() func generatereport() {
let employee1 = Employees(name: "John", money: 45000, delegate: salary) delegate.showMoney(name: name, money: money)
}
employee1.generatereport() // "The salary of John is 45000.0" }
 let salary = Salary()
var employee1 = Employees(name: "John", money: 45000, delegate: salary)
The delegate property of the Employees structure is now of type employee1.delegate = BasicSalary()
SalaryProtocol,
which means that it can store any instance of any type employee1.generatereport() // "Salary is over the minimum"

providing that it conforms to the SalaryProtocol protocol. As illustrated by this
example, the advantage of protocols is that we can use structures of
The BasicSalary structure added in Listing 3-187 conforms to SalaryProtocol
different types to perform the task. It doesn't matter what type they are as
and implements its showMoney() method, but unlike the Salary structure, it
long as they conform to the delegate’s protocol and implement its
produces two different results depending on the employee’s salary. The
properties and methods. For example, we could create two different
output produced by the execution of the generatereport() method on the
structures to print the data of our last example and assign to the delegate
Employees structure now depends on the type of structure we previously
one instance or another depending on what we want to achieve.
assigned to the delegate property.
Errors are common in computer programming. Either our code or the code When a method produces an error, it is said that it throws an error. Several
provided by libraries and frameworks may return errors. No matter how frameworks provided by Apple are already programmed to throw errors, as
many precautions we take, we can't guarantee success and many problems we will see in further chapters, but we can also do it from our own
may be found as our code tries to serve its purpose. For this reason, Swift structures and classes. To throw an error, we must use the throw and throws
introduces a systematic process to handle errors called Error Handling. keywords. The throw keyword is used to throw the error and the throws
keyword is specified in the method’s declaration to indicate that the
method can throw errors.
Because a method can throw multiple errors, we also must indicate the
type of error found with values of an enumeration type. This is a custom
enumeration that conforms to the Error protocol. For instance, let’s
consider the following example.
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 Handling Errors
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
Now that we have a method that can throw errors, we must handle the
and the returning data types), detect the error, and throw it with the throw
errors when the method is executed. Swift includes the try keyword and
keyword.
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
Listing 3-189: Throwing errors
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
enum Errors: Error {
case OutOfStock keyword in front of it.
}
struct Stock {
var totalLamps = 5
Listing 3-190: Handling errors
mutating func sold(amount: Int) throws { 
if amount > totalLamps { enum Errors: Error {
throw Errors.OutOfStock
case OutOfStock
} else {
}
totalLamps = totalLamps - amount struct Stock {
}
var totalLamps = 5
}
mutating func sold(amount: Int) throws {
} if amount > totalLamps {
var mystock = Stock()
throw Errors.OutOfStock

} else {
totalLamps = totalLamps - amount
In this example, we declare an enumeration called Errors that conforms to }
}
the Error protocol and includes a case called OutOfStock. By declaring sold() as }
a throwing method with the throws keyword, we can now throw the var mystock = Stock()
OutOfStock error every time we try to sell more lamps than we have. If the
do {
lamps sold are more than the number of lamps in stock, the method try mystock.sold(amount: 8)
throws the error, otherwise the stock is updated. } 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 var mystock = Stock()
block are executed. This pattern allows us to respond every time there is an do {
error and report it to the user or correct the situation without having to try mystock.sold(amount: 8)
} catch {
crash the app or produce unexpected results.
print(error) // OutOfStock
}
Do It Yourself: Create a new Playground file. Copy the code in Listing 
3-190 inside the file. You should see the message "We do not have
On the other hand, if we do not care about the error, we can force the try
enough lamps" printed on the console. Replace the number 8 with
keyword to return an optional with the syntax try?. If the method throws an
the number 3. Now the message should not be printed because
error, the instruction returns nil, and therefore we can avoid the use of the
there are enough lamps in stock. do catch statements.
IMPORTANT: You can add as many errors as you need to the Errors Listing 3-192: Catching errors with try?
enumeration. The errors can be checked later with multiple catch 
statements in sequence. Also, you may add all the statements you enum Errors: Error {
need to the do block. The statements before try are always executed, case OutOfStock
}
while the statements after try are only executed if no error is found. struct Stock {
var totalLamps = 5
mutating func sold(amount: Int) throws {
If the error is not one of the types we are expecting, we can print
if amount > totalLamps {
information about it. The information is stored in a constant called error throw Errors.OutOfStock
that we can read inside the catch block. } else {
totalLamps = totalLamps - amount
}
Listing 3-191: Getting information about the error }
}

var mystock = Stock()
enum Errors: String, Error { try? mystock.sold(amount: 8) // nil
case OutOfStock = "Hello" 
}
struct Stock {
var totalLamps = 5 The instruction at the end of Listing 3-192 returns the value nil if the
mutating func sold(amount: Int) throws { method throws an error, or an optional with the value returned by the
if amount > totalLamps {
throw Errors.OutOfStock method if everything goes right.
} else { Sometimes, we know beforehand that a throwing method is not going to
totalLamps = totalLamps - amount
}
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 Results
we know that the instruction will never throw the OutOfStock error.

Listing 3-193: Ignoring the errors
Sometimes we need to return more than just an error. For this purpose,

the Swift Standard Library defines the Result enumeration. This
enum Errors: Error {
case OutOfStock enumeration defines two cases with associated values to use in case of
} success or failure called success() and failure(). The Result enumeration is
struct Stock {
var totalLamps = 5 generic, which means that the data types of the associated values can be
mutating func sold(amount: Int) throws { anything we want. For instance, in the following examples we define a
if amount > totalLamps {
Result enumeration of type <Int, Errors> to return an integer and the
throw Errors.OutOfStock
} else { OutOfStock error defined in the previous example.
totalLamps = totalLamps - amount
}
} Listing 3-194: Returning an error with a Result enumeration
} 
var mystock = Stock()
enum Errors: Error {
if mystock.totalLamps > 3 { case OutOfStock
try! mystock.sold(amount: 3) }
} struct Stock {
print("Lamps in stock: \(mystock.totalLamps)") var totalLamps = 5

mutating func sold(amount: Int) -> Result<Int, Errors> {
if amount > totalLamps {
return .failure(.OutOfStock)
} else {
totalLamps = totalLamps - amount
return .success(totalLamps)
}
}
}
var mystock = Stock()
The only purpose of the get() method is to simplify the code. Now, instead
of a switch statement, we can use a do catch.
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 the strings. The orderedSame value is returned when the strings are
properties and methods. equal, the orderedAscending value is returned when the original string
precedes the value of the first argument, and the orderedDescending
capitalized—This property returns a string with the first letter of value is returned when the original string follows the value of the first
every word in uppercase. argument. The options argument is a property of the CompareOptions
length—This property returns the number of characters in the string structure. The properties available are caseInsensitive (it considers
of an NSString object. (For String values, we should use the count lowercase and uppercase letters to be the same), literal (performs a
property instead.) Byte-to-Byte comparison), diacriticInsensitive (ignores diacritic marks
localizedStringWithFormat(String, Values)—This type method such as the visual stress on vowels), widthInsensitive (ignores the width
creates a string from the string provided by the first argument and the difference in characters that occurs in some languages), and
values provided by the second argument. The first argument is a forcedOrdering (the comparison is forced to return orderedAscending or
template used to create the string, and the second argument is the orderedDescending values when the strings are equivalent but not strictly
list of values we want to include in the string separated by comma. equal). The range argument defines a range that describes the portion
contains(String)—This method returns a Boolean value that of the original string we want to compare. Finally, the locale argument
is a Locale structure that defines localization. Only the first argument is
indicates whether or not the string specified by the argument was
found inside the original string. The class also includes the required.
localizedCaseInsensitiveContains(String) method to perform a case insensitive caseInsensitiveCompare(String)—This method compares the
search that considers local conventions. original string with the string provided by the argument. It works
exactly like the compare() method but with the option caseInsensitiveSearch
trimmingCharacters(in: CharacterSet)—This method erases the
set by default.
characters indicated by the in argument at the beginning and the end
of the string and returns a new string with the result. The argument is range(of: String, options: CompareOptions, range: Range?,
a CharacterSet structure with type properties to select the type of locale: Locale?)—This method searches for the string specified by
characters we want to remove. The most frequently used are the first argument and returns a range to indicate where the string
whitespaces (spaces) and whitespacesAndNewlines (spaces and new line was found or nil in case of failure. The options argument is a property
characters). of the CompareOptions structure. The properties available for this
method are the same we have for the compare() method, with the
compare(String, options: CompareOptions, range: Range?,
locale: Locale?)—This method compares the original string with the difference that we can specify three more: backwards (searches from
the end of the string), anchored (matches characters only at the
string provided by the first argument and returns an enumeration of
type ComparisonResult with a value corresponding to the lexical order of beginning or the end, not in the middle), and regularExpression (searches
with a regular expression). The range argument defines a range that
There are different placeholders available. The most frequently used are var result = fruit.compare(search)
%d for integers, %f for floating-point numbers, %g to remove redundant 0 switch result {
(zeros), and %@ for objects and structures. We can use any of these case .orderedSame:
print("Fruit and Search are equal")
characters and as many times as necessary. This is like what we would get case .orderedDescending:
with string interpolation, but with this method we can also format the print("Fruit follows Search") // "Fruit follows Search"
case .orderedAscending:
values. For instance, we can determine the number of digits a value will print("Fruit precedes Search")
have by adding the amount before the letter. }

The range() method returns an optional value that contains the range where NSRange(Range)—This initializer creates an NSRange object from a
the string was found or nil in case of failure. In Listing 4-9, we search for the Range value.
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 NSRange(Range, in: String)—This initializer creates an NSRange
value was found) we use it to call the replaceSubrange() method of the String object to represent a Range structure with string indexes.
structure to replace the characters in the range with the string "Red" (see Range(NSRange)—This initializer creates a Range structure from an
Listing 3-67). Notice that because search values are usually provided by the NSRange value.
user, we trim the value of the search variable with the trimmingCharacters()
Range(NSRange, in: String)—This initializer creates a Range
method to make sure that there are no space characters at the beginning
structure to represent an NSRange object with string indexes.
or the end of the string (the two spaces after the word "black" are
removed).
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.
 Dates

To create your own type aliases, you can use the instruction typealias timeIntervalSince(Date)—This method compares the date in the
(e.g., typealias Myinteger = Int). Datestructure with the date specified by the argument and returns the
interval between both dates in seconds.
Besides the initializers, the class also includes type properties that return
special dates. Some of these properties produce values that are useful to
addingTimeInterval(TimeInterval)—This method adds the
set limits and sort lists, as the following. seconds specified by the argument to the date in the Date structure
and returns a new Date structure with the result.
distantFuture—This type property returns a Date structure with a addTimeInterval(TimeInterval)—This method adds the seconds
value that represents a date in a distant future. specified by the argument to the date in the Date structure and stores
distantPast—This type property returns a Date structure with a value the result in the same structure.
that represents a date in a distant past.
Comparing dates and calculating the intervals between dates is a constant
requirement in app development. The following example compares the
current date with a date calculated from a specific number of days. If the current—This type property returns a structure with the current
resulting date is later than the current date, the code prints a message on calendar set in the system.
the console to show the time remaining in seconds.
A Calendar structure includes the following properties and methods to
Listing 4-20: Comparing two dates manage the calendar and to get and set new dates.

import Foundation identifier—This property returns the value that identifies the
var days = 7 calendar.
var today = Date() locale—This property sets or returns the Locale structure used by the
var event = Date(timeIntervalSinceNow: Double(days) * 24 * 3600) Calendarstructure to process dates. The value by default is the Locale
if today.compare(event) == .orderedAscending { structure set by the system.
let interval = event.timeIntervalSince(today)
print("We have to wait \(interval) seconds")
timeZone—This property sets or returns the TimeZone structure used
} by the Calendar structure to process dates. The value by default is the

TimeZone structure set by the system.
The dates in Date structures are not associated to any calendar. This means dateComponents(Set, from: Date)—This method returns a
that to get the components in a date (year, month, day, etc.) we must DateComponentsstructure with the components indicated by the first
decide first in the context of which calendar the date is going to be argument from the date indicated by the from argument. The first
interpreted. The calendar for a date is defined by the Calendar structure. argument is a set with properties of a structure called Unit that
This structure provides properties and methods to process a date represent each component (year, month, day, hour, minute, second, etc.).
according to a specific calendar (Gregorian, Buddhist, Chinese, etc.). To
dateComponents(Set, from: Date, to: Date)—This method
initialize a Calendar structure, we have the following initializer and type
returns a DateComponents structure with the components indicated by
property.
the first argument. These components represent the difference
Calendar(identifier: Identifier)—This initializer creates a Calendar between the dates specified by the from and to arguments. The first
structure with the calendar specified by the argument. The identifier argument is a set with properties of a structure called Unit that
argument is a property of a structure called Identifier defined inside the represent each component. The most frequently used are year, month,
day, hour, minute, and second.
Calendar structure. The properties available are gregorian, buddhist, chinese,
coptic, ethiopicAmeteMihret, ethiopicAmeteAlem, hebrew, ISO8601, indian, islamic, date(byAdding: DateComponents, to: Date)—This method
islamicCivil, japanese, persian, republicOfChina, islamicTabular and returns a Date structure with the value obtained by adding the
islamicUmmAlQura. components indicated by the byAdding argument to the date
indicated by the to argument.
date(from: DateComponents)—This method returns a date
created from the components provided by the from argument. DateComponents structures are used to retrieve the components of existing
dates and to set the values for new dates. In the following example, a new
The Calendar structure works along with the DateComponents structure to read Date structure is created from the values of a DateComponents structure.
and return components from a date. The instances created from the
Listing 4-23: Creating a new date from single components
DateComponents structure include the properties year, month, day, hour, minute,

second, and weekday to read and set the values of the components. The
import Foundation
following example combines these tools to get the year of the current
date. let calendar = Calendar.current
var comp = DateComponents()
comp.year = 1970
Listing 4-21: Extracting components from a date comp.month = 8
 comp.day = 21
import Foundation var birthday = calendar.date(from: comp) // "Aug 21, 1970, 12:00 AM"

var today = Date()
let calendar = Calendar.current
var components = calendar.dateComponents([.year], from: today) The date(from:) method of the Calendar structure returns a new date with the
print("The year is \(components.year!)")

values provided by the DateComponents structure. The components which
values are not explicitly defined take values by default (e.g., 12:00 AM).
In Listing 4-21, we get a reference to the calendar set in the system from Generating a new date requires a specific calendar. For example, in the
the current property and then use the dateComponents() method to get the code of Listing 4-23, the values of the components are declared with the
year from the current date. format established by the Gregorian calendar. In this case, we rely on the
Several components may be retrieved at once by adding the corresponding calendar returned by the system, but if we want to use the same calendar
properties to the set. The following example gets the year, month, and day no matter where the app is executed, we must set it ourselves from the
from the current date. Calendar initializer.
Listing 4-22: Extracting multiple components from a date Listing 4-24: Using a Gregorian calendar


import Foundation
import Foundation
var today = Date()
let id = Calendar.Identifier.gregorian
let calendar = Calendar.current
let calendar = Calendar(identifier: id)
var comp = calendar.dateComponents([.year, .month, .day], from: today)
print("Today \(comp.day!)-\(comp.month!)-\(comp.year!)")
var comp = DateComponents()

comp.year = 1970
comp.month = 8 comp.month = 8
comp.day = 13 comp.day = 21
var birthday = calendar.date(from: comp) // "Aug 13, 1970 at 12:00 AM"
 var today = Date()
var birthdate = calendar.date(from: comp)
Declaring a specific calendar is not only recommended when creating new if let olddate = birthdate {
dates but also when calculating dates by adding components, as in the let components = calendar.dateComponents([.day], from: olddate, to: today)
following example. print("Days between dates: \(components.day!)")
}

Listing 4-25: Adding components to a date

This example calculates the days between a birthdate and the current date.
import Foundation
The value returned by the date() method used to generate the birthdate
let id = Calendar.Identifier.gregorian returns an optional, so we unwrap it before calculating the difference. We
let calendar = Calendar(identifier: id) assign this value to the olddate constant and then compare it with the
var comp = DateComponents()
comp.day = 120 current date. The number of days between dates is returned and printed
on the console.
var today = Date()
var appointment = calendar.date(byAdding: comp, to: today)
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 date() method implemented in Listing 4-25 adds components to a date
and returns a new Date structure with the result. The component day was DateInterval(start: Date, end: Date)—This initializer creates a
set to 120. The date() method takes this value, adds it to the date in the today structure with the interval between the values provided by
DateInterval
structure, and returns the result. the start and end arguments.
A common task when working with multiple dates is getting the time
between dates, such as the hours remaining for a process to complete or
DateInterval(start: Date, duration: TimeInterval)—This
the days remaining for an event to begin. The Calendar structure includes a initializer creates a DateInterval structure with an interval that starts at
version of the dateComponents() method that allows us to compare two dates the date specified by the start argument and last as long as the time
and get the difference expressed in a specific component. specified by the duration argument.
Listing 4-26: Comparing dates The DateInterval structure also offers the following properties and methods.

import Foundation start—This property sets or returns the initial Date of the interval.
let calendar = Calendar.current end—This property sets or returns the final Date of the interval.
var comp = DateComponents()
comp.year = 1970
duration—This property sets or returns the duration of the interval print("You still have time") // "You still have time"
}
in seconds. }

contains(Date)—This method returns a Boolean value that indicates
whether the date specified by the argument is inside the interval or The code in Listing 4-27 creates two dates, birthday and future, and then
not. generates an interval from one date to another. The contains() method is
intersects(DateInterval)—This method returns a Boolean value used next to check whether the current date is within the interval or not.
that indicates if the interval intersects with the interval specified by As with numbers, Foundation also provides the tools to format dates. The
the argument. Date structure defines two versions of the formatted() method for this
purpose.
intersection(with: DateInterval)—This method returns a
value with the interval in which the original interval and the
DateInterval
formatted(date: DateStyle, time: TimeStyle)—This method
one provided by the with argument overlap.
formats the date with the styles specified by the arguments. The date
argument defines the style for the date. It is a structure with the type
A typical use of the DateInterval structure is to create an interval from two
properties abbreviated, complete, long, numeric, and omitted. And the time
dates and check if a specific date falls within the interval, as in the
following example. argument defines the style for the time. It is a structure with the type
properties complete, omitted, shortened, and standard.
Listing 4-27: Finding a date in an interval formatted(FormatStyle)—This method formats the date with the
 styles specified by the argument.
import Foundation
The code in Listing 4-28 gets the current date from the now property and
then calls the method with the abbreviated and omitted values. This creates a The FormatStyle structure includes the following properties to configure the
string that contains a date with abbreviated text and no time ("Jun 18, parameters used to format the date.
2021").
Standard styles include all the components of the date, but the Date calendar—This property sets or returns the calendar used to format
structure includes an additional version of the formatted() method that takes the date. It is of type Calendar.
a FormatStyle structure to format the date any way we want. The following
locale—This property sets or returns the locale used to format the
are some of the methods included for customization.
date. It is of type Locale.
day(Day)—This method includes the day. The argument defines the timeZone—This property sets or return the time zone used to
style for the day. It is a structure with the properties defaultDigits, format the date. It is of type TimeZone.
ordinalOfDayInMonth, and twoDigits.
Although we can create our own FormatStyle structure, the structure
month(Month)—This method includes the month. The argument includes a type property called dateTime to return an instance with the
defines the style for the month. It is a structure with the properties calendar and standard values set by the device. If the configuration by
abbreviated, defaultDigits, narrow, twoDigits, and wide. default is enough, we can use this property to format the date, as shown
year(Year)—This method includes the year. The argument defines next.
the style for the year. It is a structure with the properties defaultDigits
Listing 4-29: Specifying a custom format
and twoDigits.

hour(Hour)—This method includes the hour. The argument defines import Foundation
the style for the hour. It is a structure with the properties
let mydate = Date.now
defaultDigitsNoAMPM and twoDigitsNoAMPM. let text = mydate.formatted(.dateTime.weekday(.wide))
print(text) // "Friday"
minute(Minute)—This method includes the minutes. The argument 
defines the style for the minutes. It is a structure with the properties
defaultDigits and twoDigits. The code in Listing 4-29 calls the weekday() method from the FormatStyle
second(Second)—This method includes the seconds. The argument structure returned by the dateTime property to get the day of the week. In
defines the style for the seconds. It is a structure with the properties 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
defaultDigits and twoDigits.
we can add more by concatenating the methods with dot notation, as we
weekday(Weekday)—This method includes the weekday. The did before for numbers.
argument defines the style for the day. It is a structure with the
properties abbreviated, narrow, oneDigit, short, twoDigits, and wide. Listing 4-30: Including multiple date components
 The FormatStyle structure includes the following method to format a date for
import Foundation a locale.
let mydate = Date.now
let text = mydate.formatted(.dateTime.day().hour().month(.wide)) locale(Locale)—This method specifies the locale to use by the
print(text) // "June 18, 6 PM"
 formatter.
In this code, we implement the day(), month(), and hour() methods. The result Although it is recommended to use the Locale structure set by the system
is a string with a date that includes the month (full name), the day, and the and keep the values by default, there are times when our application must
hour ("June 18, 6 PM"). present the information with a specific configuration. For example, we may
Notice that the order in which the methods are called doesn't matter. The need to create an application that always shows dates in Chinese, no
date and time are always formatted with a standard format that depends matter where the user is located. We can do this by defining a Locale
on the user's locale (language and country). This is because the formatted() structure and then include the locale() method in the formatter with this
method processes dates according to local conventions, including the
value.
language, symbols, etc. This means that the components of a date are
interpreted according to the conventions currently set on the device. For Listing 4-31: Specifying a different locale
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 import Foundation
China. How dates are processed is determined by an object of the Locale
let mydate = Date.now
structure. Every device has a Locale structure assigned by default, and our let chinaLocale = Locale(identifier: "zh_CN")
code will work with it unless we determine otherwise. To get a reference to let text = mydate.formatted(.dateTime.locale(chinaLocale).day().month().year())
the current structure or create a new one, the Locale structure includes the 年月 日
print(text) // "2021 6 18 "

following initializer and type property.
This example creates a new Locale structure with the zh_CN identifier,
Locale(identifier: String)—This initializer creates a Locale structure
which corresponds to China and the Chinese language, and then formats
configured for the region determined by the value of the argument.
the date with this locale and the day(), month(), and year() methods. The result
The argument is a string that represents a language and a region (e.g.,
is a string with the date in Chinese.
en_US for the United States, zh_CN for China).
current—This type property returns the Locale structure assigned by IMPORTANT: The list of identifiers you can use to create a Locale
default to the device or defined by the user in the Settings app. structure is extensive. You can print the type property availableIdentifiers
from the Locale structure to get an array with all the values available.
The date stored in a Date structure is not a date but the number of seconds let tokyoTime = mydate.formatted(dateTimeStyle.hour().minute().second())
between the date represented by the object and an arbitrary date in the dateTimeStyle.timeZone = madridTimeZone
past (January 1st, 2001). To process these values and get the actual date, let madridTime = mydate.formatted(dateTimeStyle.hour().minute().second())
the Calendar structure needs to know the user's time zone. Foundation print("My Time: \(mytime)") // "My Time: 9:25:19 PM"
print("Tokyo Time: \(tokyoTime)") // "Tokyo Time: 10:25:19 AM"
includes the TimeZone structure to manage time zones. An object is assigned print("Madrid Time: \(madridTime)") // "Madrid Time: 3:25:19 AM"
by default to the system containing the time zone where the device is }

located (that is why when we display a date it coincides with the date in
our device), but we can define a different one as we did with the Locale
The code in Listing 4-32 creates two TimeZone structures, one for Tokyo's
structure. To get a reference to the current structure or create a new one,
time zone and another for Madrid's. If successful, we initialize a FormatStyle
the TimeZone structure includes the following initializer and type property.
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
TimeZone(identifier: String)—This initializer creates a TimeZone
FormatStyle structure before using it to format each date.
structure configured for the time zone determined by the value of the
identifier argument. The argument is a string that represents the
IMPORTANT: The list of names for the time zones is stored in a
name of the time zone (e.g., "Europe/Paris", "Asia/Bangkok"). database. The TimeZone structure offers the knownTimeZoneIdentifiers
current—This type property returns the TimeZone structure assigned type property that you can print to see all the values available.
by default to the device or defined by the user in the Settings app.
but Foundation includes the Measurement structure to simplify our work. This ounces, pounds, stones, metricTons, shortTons, carats, ouncesTroy, and slugs, with
structure includes two properties, one for the value and another for the kilograms defined as the basic unit.
unit. The initializer requires these two values to create the structure. UnitVolume—This subclass defines the units of measurement for
volume. It includes the following properties to represent the units:
Measurement(value: Double, unit: Unit)—This initializer creates megaliters, kiloliters, liters, deciliters, centiliters, milliliters, cubicKilometers,
a Measurement structure with the values specified by the value and unit cubicMeters, cubicDecimeters, cubicMillimeters, cubicInches, cubicFeet, cubicYards,
arguments. The unit argument is a property of a class that inherits cubicMiles, acreFeet, bushels, teaspoons, tablespoons, fluidOunces, cups, pints,
from the Dimension class. quarts, gallons, imperialTeaspoons, imperialTablespoons, imperialFluidOunces,
imperialPints, imperialQuarts, imperialGallons, and metricCups, with liters
The value declared for the Measurement structure is a number that defined as the basic unit.
determines the magnitude, like 55 in 55 km, and the unit is a property of a
subclass that inherits from the Dimension class and represents the unit of The Measurement structure includes the following properties and methods to
measurement, like km in 55 km. The Dimension class contains all the basic access the values and convert them to different units.
functionally required for measurement but is through its subclasses that
the units of measurement are determined. Foundation offers multiple value—This property sets or returns the structure's value. It is of type
subclasses for this purpose. The following are the most frequently used. Double.

The initialization of a Measurement structure is simple, we just need to import Foundation
provide the value for the magnitude and the property that represents the var length = Measurement(value: 300, unit: UnitLength.meters)
unit of measurement we want to use. The following example creates two var width = Measurement(value: 2, unit: UnitLength.kilometers)
structures to store a measurement of 30 centimeters and another of 5 var total = length + width // 2300.0 m
pounds. 
Listing 4-33: Initializing Measurement structures The code in Listing 4-35 adds two lengths of different units (meters and
 kilometers). The system converts kilometers to meters and then performs
import Foundation the addition, returning a Measurement structure with a value in meters (the
var length = Measurement(value: 30, unit: UnitLength.centimeters) // 30.0 cm basic unit).
var weight = Measurement(value: 5, unit: UnitMass.pounds) // 5.0 lb 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
If the measurements are of the same dimension (e.g., length), we can following example, we convert the unit of the length variable to kilometers
perform operations with their values. The Measurement structure allows the and perform the addition again in kilometers.
operations +, -, *, /, and also the use of the comparison operators ==, !=, <, >,
Listing 4-36: Converting units
<=, and >= to compare values. The following example adds two

measurements in centimeters. import Foundation
Listing 4-34: Adding the values of two Measurement structures var length = Measurement(value: 300, unit: UnitLength.meters)
var width = Measurement(value: 2, unit: UnitLength.kilometers)

length.convert(to: UnitLength.kilometers)
import Foundation
var total = length + width // 2.3 km
var length = Measurement(value: 200, unit: UnitLength.centimeters) 
var width = Measurement(value: 800, unit: UnitLength.centimeters)
var total = length + width // 1000.0 cm 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
If the units are different, the Measurement structure returned by the
the formatted() method.
operation is defined with the dimension's basic unit. For example, if we are
working with lengths, the basic unit is meters.
formatted(FormatStyle)—This method formats the measurement
Listing 4-35: Adding two values of different units with the styles specified by the argument.
The width argument specifies how the unit is going to be displayed. It
This method requires a FormatStyle structure to format the value. The is a structure with the properties abbreviated, narrow, and wide. The
structure includes the following initializer and type method to create an locale argument specifies the locale. The usage argument specifies
instance for every type of unit. the purpose of the measurement. The structure to declare this value
includes properties for any type of measurement. The ones available
FormatStyle(width: UnitWidth, locale: Locale, usage: at the moment are asProvided (UnitType), food (UnitEnergy), general
MeasurementFormatUnitUsage, numberFormatStyle: (UnitType), person (UnitTemperature), personHeight (UnitLength),
FloatingPointFormatStyle)—This initializer creates a FormatStyle personWeight (UnitMass), road (UnitLength), weather (UnitTemperature),
structure with the format set by the arguments. The width argument
and workout (UnitEnergy). The hidesScaleName argument determines
specifies how the unit is going to be displayed. It is a structure with
if the name of the unit is going to be displayed, and the
the properties abbreviated, narrow, and wide. The locale argument
numberFormatStyle argument specifies the format of the value.
specifies the locale. The usage argument specifies the purpose of the
measurement(width: UnitWidth, locale: Locale, usage:
measurement. The structure to declare this value includes properties
MeasurementFormatUnitUsage, hidesScaleName: Bool,
for any type of measurement, including asProvided (UnitType), food
numberFormatStyle: FloatingPointFormatStyle)—This method
(UnitEnergy), general (UnitType), person: (UnitLength), personHeight
returns a FormatStyle structure with the format set by the arguments.
(UnitLength), personWeight (UnitMass), road (UnitLength), weather
The arguments are the same required by the initializer.
(UnitTemperature), and workout (UnitEnergy). Finally, the
numberFormatStyle argument specifies the format of the value.
Most of the arguments in these initializers and methods are optional. If an
measurement(width: UnitWidth, usage: argument is not declared, the formatter uses values by default. For
MeasurementFormatUnitUsage, numberFormatStyle: instance, if we just want to show the full name of the unit, we can
FloatingPointFormatStyle)—This method returns a FormatStyle implement the measurement() method with the width argument and the
structure with the format set by the arguments (the same required by value wide.
the initializer).
Listing 4-37: Formatting a measurement
The FormatStyle structure also includes an additional initializer and method 
specific to format temperatures. import Foundation
In this example, we have also included the usage argument with the The formatted() method in this example includes the width argument to get
asProvided value to tell the formatter to use the original units (kilometers). If the unit's full name, the usage argument to format the value to represent a
this argument is not declared, the formatter uses the value by default, distance, and the numberFormatStyle argument to format the value. The
which formats the measurement with the configuration and locale set in value is expressed in miles again, but with better accuracy.
the device. For instance, if we specify the road value instead, the formatter If our application has to display a value always with the same unit of
will format the value to represent a distance using the device's locale, measurement independently of the device's location, we can set a specific
which for a device running in the United States means that the original locale. The formatted() method doesn't include an argument to designate a
value will be expressed in miles. locale, but the initializers included in the FormatStyle structure do. The
Listing 4-38: Formatting a measurement for a specific purpose following example formats the measurements in Chinese, no matter where
 the device is located.
import Foundation
Listing 4-40: Formatting a measurement for a specific locale
let length = Measurement(value: 40, unit: UnitLength.kilometers) 
let text = length.formatted(.measurement(width: .wide, usage: .road))
import Foundation
print(text) // "25 miles"

let length = Measurement(value: 40, unit: UnitLength.kilometers)
let chinaLocale = Locale(identifier: "zh_CN")
By default, the formatter rounds the number. That's why in this example var format = Measurement<UnitLength>.FormatStyle(width: .wide, locale: chinaLocale,
usage: .asProvided)
the result of converting 40 kilometers to miles is 25, when it should've let text = length.formatted(format)
been 24.8548. If we want to specify a different format, we can add the 公里
print(text) // "40.00 "
numberFormatStyle argument and implement the methods provided by 
fire()—This method fires the timer without considering the time var counter = 0
remaining. func startTimer() {
invalidate()—This method invalidates the timer (stops the timer). Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timerref) in
report(timer: timerref)
}
}
A regular expression is a sequence of characters that represent the pattern *specifies 0 or more occurrences of the previous character.
we want to identify on a string. It is composed of common characters that +specifies 1 or more occurrences of the previous character.
match the characters in the string one by one, and also special characters ? specifies 0 or 1 occurrences of the previous character.
that describe specific patterns or matches. For instance, the expression {min, max} specifies the minimum and maximum occurrences of the
/message/ will match any string that contains the word "message", but we can previous character (e.g., {3, 5} matches 3, 4, or 5 characters). If the
add the special characters \s+ at the end to match a string that contains the maximum value is ignored, the expression matches the character at
word "message" followed by one or more spaces (/message\s+/). least the times specified by the minimum value (e.g., {2,} matches the
To differentiate special characters from common characters, they are character at least two times). And if only one value is declared, the
preceded by a backward slash, as in \s (the s represents a white space). expression matches the character exactly the times specified by the
There are dozens of special characters to represent anything we need. The value (e.g., {2} matches the characters only two times).
following are the most frequently used.
There are also special characters to delineate the expression. The following
\sand \t match a whitespace. The \s character matches any whitespace are the most frequently used.
character, and the \t character matches only tab characters.
\d match a digit (any number from 0 to 9). ^ matches the beginning of the string.
$ matches the end of the string.
() define a subexpression (useful when we want to capture the value).
To match a character from a group of characters, regular expressions
include the following special characters.
Regular expressions are defined by a combination of these special
[] match any character in the square brackets (e.g., [abc] matches the characters and also common characters, as we have seen before. For
character a or b or c). It can also match a character in a range of instance, if we have a string like "Name: John" and we want to know
consecutive characters. The range is defined with a hyphen, as in [a-z] whether the string contains any name, we can match it against the regular
to match a lowercase letter from a to z, or [a-zA-Z] to match a lowercase expression /Name:\s+[a-zA-Z]+/. The slashes at the beginning and the end are
or uppercase letter from a to z. the way to tell Swift that this is a regular expression. The string "Name:"
[^] match any character different from those in the square brackets matches exactly the same sequence of characters in the original string. The
(e.g., [^abc] matches any character that is not a or b or c). \s+ characters tell the system that the string "Name:" must be followed by
. (dot) matches any character except a new line (new lines are
one or more spaces. And the [a-zA-Z]+ characters indicate that after the
represented by the special character \n).
space there should be one or more lowercase or uppercase letters. If the
| (or) matches the character on the left or the right (e.g., a|b matches
original string doesn't contain the string "Name:" followed by one or more
the character a or the character b).
spaces and one or more letters, we won't get a match.
To match the regular expression against a string, the Swift Standard Library This code defines a regular expression and assigns it to a constant. The
includes the following methods. compiler detects that the value is a regular expression and creates a Regex
structure with it. Next, the firstMatch() method matches the string in the
firstMatch(of: Regex)—This method finds the first sequence of message constant against the regular expression and returns a value with the
characters in the string that match the regular expression specified by result. In this case, the string contains the string "Name:"followed by one
the of argument. or more spaces and one or more letters, so the match is successful. The
wholeMatch(of: Regex)—This method matches the whole string part of the string that matches the regular expression is assigned to the
output property of the Match structure returned by the method, so we assign
against the regular expression.
it to a constant and print it on the console.
prefixMatch(of: Regex)—This method matches the beginning of
the string against the regular expression.
IMPORTANT: Declaring the regular expression between slash
matches(of: Regex)—This method finds all the sequences of characters, as we did in our example, allows Xcode to recognize it
characters in the string that match the regular expression. and provide feedback. If the expression is invalid, it is displayed in a
single color, otherwise, the special characters are highlighted. This is
These methods take a value of type Regex (a structure defined to contain a the recommended notation in Swift, but the language provides
regular expression) and return a value of type Match if a match is found or others. For instance, we can create the Regex structure directly with
nil otherwise. The Match structure contains two properties: output with the the structure's initializer and the regular expression in quotes. Swift
strings that matched the regular expression, and range with a Range value also allows us to declare the regular expression using different
that represents the range of the overall match. syntaxes, including an extended syntax that can be used to provide
For instance, the following code processes the string and regular names and define the data types of the values we captured. For
expression mentioned before with the firstMatch() method to check if the more information, visit our website and follow the links for this
string contains a name. chapter.
Listing 4-43: Using regular expressions The values returned in the Match structure depend on the expression. In the
 previous example, the output property returns a single string with the
let message = "Name: John" sequence of characters that match the regular expression, but we can
let regex = /Name:\s+[a-zA-Z]+/
define subexpressions to capture specific values. For instance, if we want
to get back the name, we can define a subexpression with parentheses.
if let match = message.firstMatch(of: regex) {
let found = match.output
print("Found: \(found)") // "Found: Name: John" Listing 4-44: Capturing values with subexpressions
} 
 let message = "Name: John"
let regex = /Name:\s+([a-zA-Z]+)/
split(separator: Regex)—This method divides a string by a
if let match = message.firstMatch(of: regex) { separator and returns an array with the parts. The separator
let found = match.output.1
print("The name is: \(found)") // "The name is: John"
argument specifies the regular expression that matches the characters
} used to split the string.

replacing(Regex, with: String)—This method replaces the
sequence of characters that match the regular expression specified by
In this example, the output property returns a tuple. The first element of the
the first argument with the string specified by the with argument.
tuple is the string that matches the regular expression, as before, but the
second value is the string that matches the expression between
The split() method is useful when we have to process long texts or a text file
parenthesis. (In this case, one or more letters after a space.)
line by line. In the following example, we use triple quotes to tell the
The firstMatch() method finds the first sequence of characters that match the
compiler to add the characters \n after each line of text to generate a new
regular expression and returns that value. If we want to get all the matches
line (see Listing 2-24). This is how usually text files are structured. To read
in a string, we must implement the matches() method.
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.
Listing 4-45: Capturing multiple values

Listing 4-46: Processing multiple lines of text with a regular expression
let message = "Name: John, Name: George, Name: David"
let regex = /Name:\s+([a-zA-Z]+)/ 
let message = """
let matches = message.matches(of: regex) John
if !matches.isEmpty { George
let names = matches.map({ value in Martin
return value.output.1 """
}) let separator = /\n/
let list = names.joined(separator: ", ") let lines = message.split(separator: separator)
print("Names are: \(list)") // "Names are: John, George, David" print(lines) // ["John", "George", "Martin"]
} 

The replacing() method could be implemented in a similar fashion, but
The matches() method returns an array of Match structures. Again, we use a instead of using the regular expression as a separator, we can use it to
subexpression to capture the names, so we loop through the array with the replace the sequence of characters with another one. For instance, we can
map() function, return the name from each match, and then join them with replace each new-line character in our string (\n) with a comma to get all
the joined() method to get the list of names separated by comma. the lines of text in a single string.
The Swift Standard Library includes a few more methods to take advantage
of regular expressions. The following are the most frequently used. Listing 4-47: Replacing the characters that match a regular expression

 import RegexBuilder
import RegexBuilder
let message = "09 units, stock 190"
let regex = Regex {
let regex = Regex { Capture {
"Name:" OneOrMore(.digit)
OneOrMore(.whitespace) } transform: { value in
Capture { Int(value)
One(CharacterClass("A"..."Z")) }
OneOrMore {
CharacterClass("a"..."z") OneOrMore(.any, .reluctant)
} Capture {
} OneOrMore(.digit)
} } transform: { value in
let message = "Name: John" Int(value)
let result = message.matches(of: regex) }
if let name = result.first?.output.1 { }
let result = message.matches(of: regex)
if let units = result.first?.output.1, let stock = result.first?.output.2 { localizedCurrency(code: Currency, locale: Locale)—This method
let total = units + stock
returns a FormatStyle structure to match currency values. The code
print("Final Stock: \(total)") // "Final Stock: 199"
} argument can be specify as a string to represent the currency (e.g.:
 "USD"). The locale argument specifies the locale.
date(DateStyle, locale: Locale, timeZone: TimeZone, calendar:
The string we are processing includes numbers at the beginning and the
Calendar?)—This method returns a ParseStrategy structure to match a
end. The first number represents the units to be added to the stock, and
date. The first argument is a DateStyle structure that specifies the date's
the last number represents the current stock. To get the first number as an
format. The structure includes the type properties abbreviated, complete,
integer, we capture one or more digits. (The digit property returns a
long, numeric, and omitted. The locale argument specifies the locale, the
CharacterClass structure that represents numbers between 0 and 9.)
timeZone argument the time zone, and the calendar argument the
Capturing the last number is a bit tricky. Because we may have any
calendar we want to use to interpret the date.
characters in between, to match the text we use the any property, which
returns a CharacterClass structure that represents any character. But this dateTime(date: DateStyle, time: TimeStyle, locale: Locale,
includes numbers, which means that the number at the end will also be timeZone: TimeZone, calendar: Calendar?)—This method
matched by this pattern and the last Capture structure won't have anything returns a ParseStrategy structure to match date and time. The date
else to match. By default, the behavior of the OneOrMore structure is argument is a structure that specifies the date's format. The structure
determined by the RegexRepetitionBehavior structure returned by the eager includes the type properties abbreviated, complete, long, numeric, and
property. This structure tells the system to match as many characters as it omitted. The time argument is a structure that specified the time's
can. But we can change this behavior by assigning the reluctant property format. The structure include the type properties complete, omitted,
instead, as we did in our example. The structure returned by this property shortened, and standard. The locale argument specifies the locale, the
asks the system to match the current expression until any character timeZone argument the time zone, and the calendar argument the
matches the next expression, so when the number is found, the system calendar we want to use to interpret the date and time.
moves to the next OneOrMore structure that matches digits and we are able
to capture our second number. The following example shows how to match dates and currency. In this
Although we can transform any value we want, some like dates and case, we process a string with multiple items and split the content in lines
complex numbers may require long patterns. To simplify our work, the of text to process each value, as we did in Listing 4-46.
Foundation framework includes properties and methods we can use to
match these values. The following are the most frequently used. Listing 4-51: Matching dates and currency

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.
CGFloat—This structure is used to store values of type Double for There is also a more complex structure called CGRect that we can use to
drawing purposes. define and work with rectangles. This data type includes the following
initializers and properties.
A more complex structure is CGSize, designed to store values that represent
dimensions (width and height). This data type includes the following CGRect(origin: CGPoint, size: CGSize)—This initializer creates a
initializer and properties. CGRect structure to store the origin and size of a rectangle. The origin
argument is a CGPoint structure with the coordinates of the rectangle's
CGSize(width: CGFloat, height: CGFloat)—This initializer creates origin, and the size argument is a CGSize structure with the rectangle's
a CGSize structure with the values specified by the width and height width and height.
arguments. The structure defines initializers to create instances from CGRect(x: CGFloat, y: CGFloat, width: CGFloat, height:
values of type Int, CGFloat, and Double. CGFloat)—This initializer creates a CGRect structure to store the
zero—This type property returns a CGSize structure with its values set origin and size of a rectangle. The x and y arguments define the
to zero. coordinates of the rectangle's origin, and the width and height
width—This property sets or returns the structure's width. arguments its size. The structure defines initializers to create instances
from Int, CGFloat, and Double values.
height—This property sets or returns the structure's height.
zero—This type property returns a CGRect structure with its values set
Another structure included in the framework is CGPoint, which is used to to zero.
define points in a two-dimensional coordinate system. It includes the origin—This property sets or returns a CGPoint structure with the
following initializer and properties. coordinates of the rectangle's origin.
size—This property sets or returns a CGSize structure with the print("The origin is at \(myrect.origin.x) and \(myrect.origin.y)")
print("The size is \(myrect.size.width) by \(myrect.size.height)")
rectangle's width and height. 
midX—This property returns the value of the rectangle’s x coordinate
located at the horizontal center of the rectangle. The framework also The origin and size properties of a CGRect value are CGPoint and CGSize
structures, respectively, so they can be copied into other variables or
include the minX and maxX properties to return the smallest and
properties as any other values.
largest value in the x-coordinate for the rectangle.
midY—This property returns the value of the rectangle’s y coordinate Listing 4-54: Accessing the structures inside a CGRect structure
located at the vertical center of the rectangle. The framework also 
include the minY and maxY properties to return the smallest and import CoreGraphics
largest value in the y-coordinate for the rectangle. var myrect = CGRect(x: 30, y: 20, width: 100, height: 200)
Listing 4-56: Calculating the coordinate at the center of the rectangle CHAPTER 5 - SWIFTUI FRAMEWORK

import CoreGraphics
Up to this point, we have been working with a simplified interface called Applications are built from several files and resources, including our own
Playground. Playground was designed to reduce the learning curve and to codes, frameworks, images, databases, and more. Xcode organizes this
allow developers to test code, but to build functional applications we must information in projects. An Xcode project includes all the information and
resources necessary to create an application. The welcome window,
move to the Xcode’s main interface. This is a toolset that comprises an
illustrated in Figure 1-2, presents a button called Create a new Xcode
editor, a canvas for displaying previews, resource managers, configuration
project to initiate a project. When this button is clicked, a window appears
panels, and debugging tools, all integrated into a single workspace where
to select the type of project we want to create.
we can create our applications.
Figure 5-1: Selecting the type of project
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
The Product Name is the name of the project. By default, this is the name Next button to select the folder where our project is going to be stored.
of our app, so we should write a name that is appropriate for our Xcode creates a folder for each project, so we only have to designate the
application (this value can be modified later). Next is the Team's account. destination folder where we are going to store all our projects and
This is the developer account created with our Apple ID or our company's everything else is generated for us.
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 Do It Yourself: Open Xcode. In the welcome screen, click on the
Identifier. Xcode uses this value to create a unique identifier for our app option Create a new Xcode project (Figure 1-2). You can also go to
and therefore it is recommended to declare it with an inverted domain, as the File menu at the top of the screen and select the options
we did in our example (com.formasterminds). Using the inverted domain New/Project. After this, you should see the projects window (Figure
ensures that only our app will have that identifier. And finally, there is a list 5-1). Select the Multiplatform tab, click on the App icon, and press
of options we can select to incorporate additional functionality to our Next. Now you should see the form of Figure 5-2. In Product Name,
project, such as Core Data and Tests. We will explore these alternatives in Insert the name of your app. In the Team option, select your
further chapters, but should be left unchecked if we are not planning to
developer account or press the Add Account button to add it to
use them.
Xcode. In Organization Identifier insert the inverted domain of your
As mentioned above, the Team's field shows a list of developer accounts
website or blog. Make sure that the rest of the options are disabled.
registered with Xcode. If we haven't yet inserted our account, we will see
the Add Account button instead. Pressing this button opens Xcode Finally, press Next and select a folder where to store the project.
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.) IMPORTANT: Although it’s not mandatory, you should get your own
domain and website. Apple not only recommends the use of an
Figure 5-3: Registering our Apple account in Xcode 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 two buttons to show or hide the removable panels (number 3 and
support (see Chapter 20). number 4).
Navigator Area—This is a removable area that provides information
Once the project is created, Xcode generates some files according to the
about the files that comprise the application and tools for debugging
selected template and presents the main interface on the screen. Figure 5-
(identify and remove errors in the code). From here, we can select the
4 shows what this interface looks like.
files to edit, create groups to organize them, add resources, check for
Figure 5-4: Xcode’s interface errors, and more. In addition to the files, this area shows an option at
the top to configure the app (number 6).
Editor Area—This is the only non-removable area and is the one
where we will do much of the work. The content of files and
configuration panels are displayed here. Although the Editor Area
cannot be removed, it includes buttons at the top to split the area
into multiple editors or panels, as we will see later (number 8).
Debug and Console Area—This is a removable area with two
sections. The section on the left provides information for debugging,
while the section on the right is a console to display the results of the
execution of our code, including warnings and errors. The panel can
 be shown or hidden from the button at the top-right corner (number
9), and each section can be shown or hidden from the buttons at the
Like the Playground interface, the Xcode's main window is organized in bottom-right corner (number 7).
several areas. There is a toolbar at the top, an area to edit the files at the Utilities Area—This is a removable area that provides additional
center called Editor Area, and three removable panels on the sides called information about the app and tools to edit the interface.
Navigator Area, Debug and Console Area, and Utilities Area.
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 In SwiftUI, the most useful options are the Canvas, the Layout, and the
Editor button. Two editors are shown side by side. 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
Figure 5-5: Multiple editors in the Editor Area
shows a visual representation of our code that we can use for reference
and navigation.
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 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
Figure 5-6: Adjust Editor Options
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. 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
Figure 5-8: Scheme menu opens where we can see our app and interact with it, as shown below.
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. 
Figure 5-9: Options available to run the app 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, From the General panel we can change basic aspects of the application
and turn Developer Mode on, as shown below. such as the devices the app will support, the app's name, version, available
orientations for iPhones and iPads, and the deployment target (the
Figure 5-11: Developer mode 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.

Figure 5-13: App configuration
Do It Yourself: If you haven't done it yet, create a new Multiplatform
project with the App template following the steps described above.
From the toolbar, select an iPhone simulator (Figure 5-9). Press the
Play button to run the app (Figure 5-4, number 1). You should see
the simulator on the screen with an icon and the text "Hello World"
at the center (Figure 5-10). 
The app's configuration is stored on a target. The target includes things like The information in this panel is stored in a plist file; which is a file with a
the app's name and version, the systems it will support, and the format that allows us to store keys and values. The keys represent
capabilities the app will have, such as access to the camera or iCloud configuration options and the values are what we want to assign to our
servers. The option to change these values is available in the Navigator app. These configuration options include those declared from other panels,
Area (Figure 5-4, number 6). Once we click on it, a series of panels are like the available orientations set in the General panel, and also custom
displayed in the Editor Area (Figure 5-12, number 1). options, like the image we want to show when the app is launched, as we
will see later.
Figure 5-12: Target configuration
SwiftUI Files }


Everything in SwiftUI is created inside structures. As illustrated by the code
The Multiplatform App template includes two Swift files: one with the code in Listing 5-1, the initial configuration of the application is defined inside a
to initialize the app (TestApp.swift in our example) and another to define structure that conforms to the App protocol. This is a protocol declared in
the user interface (ContentView.swift). Figure 5-14, below, shows the the SwiftUI framework to define the structure and behavior of an
Navigator Area with all the items created by the template. application. The protocol's only requirement is the definition of a
computed property called body that determines the app's content and
Figure 5-14: Files created by the Multiplatform App template returns a Scene (window).
The App structure must be preceded by the @main attribute, which indicates
the app's point of entry. When the app is launched, the system looks for a
structure that conforms to the App protocol and is preceded by the @main
attribute, creates an instance of that structure, and then executes the code
in the body property.
In Apple devices, we can open multiple instances of an app. For instance, in
iPads and Mac computers, we can open two or more copies of the same
app to process different information, such as two windows of the Text
 editor to process two different documents. To manage these instances, the
system implements Scenes.
The first file is named after the application and includes the App suffix to A Scene is a structure that conforms to the Scene protocol; a SwiftUI
indicate that the code it contains is in charge of initializing the app protocol defined to manage the app's interface and adapt it to every
(TestApp in our example). The following is the code generated by the platform. Although we can create our own Scenes by defining a structure
template for this file. that conforms to this protocol, SwiftUI includes several structures to create
standard Scenes for every system. The currently available are WindowGroup,
Listing 5-1: Initializing the app Window, DocumentGroup, Settings, and MenuBarExtra. Some of these structures
 are designed to create Scenes for Mac computers, as we will see later, and
import SwiftUI others are more generic. For instance, the WindowGroup structure can
@main
manage multiple windows for iPhones, iPads and Macs, and therefore it is
struct TestApp: App { the one recommended for most projects. The following is the structure's
var body: some Scene { initializer.
WindowGroup {
ContentView()
}
}
WindowGroup(Text, id: String, content: Closure)—This SwiftUI views are defined by structures. In the example provided by the
initializer creates a Scene to manage all the windows of an instance of App template, the closure assigned to the WindowGroup structure initializes
the application. The first argument defines the window's title, the id and returns a structure called ContentView. This is a custom structure
argument specifies the window's identifier, and the content argument included by the template to define the initial view. (The initial view
is a closure that defines what the windows are going to display. 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.
Due to its capacity to create applications for every device, the WindowGroup
structure is the one implemented in the template of a Multiplatform
Listing 5-2: Defining a structure that conforms to the View protocol
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
import SwiftUI
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.
struct ContentView: View {
The WindowGroup structure creates a Scene with a window. Windows
var body: some View {
determine the space where the graphics are displayed, but they do not
VStack {
generate any visible content. The user’s interface is built inside a window Image(systemName: "globe")
from similar containers called Views. These views are rectangular areas of .imageScale(.large)
.foregroundColor(.accentColor)
custom size, designed to display graphics on the screen. Some views are Text("Hello, world!")
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. 
Figure 5-15: Views hierarchy As illustrated by the code in Listing 5-2, a structure that defines a SwiftUI
view must conform to the View protocol. This is another protocol defined in
the SwiftUI framework which only requirement is the implementation of a
computed property called body that returns at least one view. Within the
closure assigned to this property is where we declare all the views we need
to design the user interface, as we will see later.
When the system processes a View structure, it creates a view called root
view, and then determines its size from the size of its container. This
container can be the window or another view. For instance, the ContentView
 view created by the App template is assigned as the window's root view by
the WindowGroup structure, and therefore its size is going to be determined
by the size of the window.
The views defined in the body property follow a different path. They Canvas
determine their size from the size of the container, but then they propose

that size to their content and it is the content that finally decides which
size the view is going to take.
SwiftUI files include the definition of two structures, one to declare the
We can see this at work when we preview on the canvas the interface
view and another to create the preview on the canvas. This second
produced by the ContentView structure created by the template. The
structure conforms to the PreviewProvider protocol which requires the
structure creates a white view that occupies the entire screen and then
implementation of a type property called previews that returns the view or
two small views inside, one with an icon and another with the text "Hello
group of views to display on the canvas. The following is the preview
World!", as shown below.
structure defined by the App template for the ContentView view.
Figure 5-16: Views created by the App template on the Canvas
Listing 5-3: Defining the Canvas preview

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.
Text View This example displays the value as it is, but we can use the formatted()
method to format the value, as we did in Chapter 4. For instance, we can

turn the number into currency to represent US Dollars.
The Text structure takes a string and returns a view that displays the text on
Listing 5-7: Formatting the value for the Text view
the screen. The structure defines multiple initializers. The following are the

most frequently used.
struct ContentView: View {
let number: Float = 30.87512
Text(String)—This initializer creates a Text view with the text defined
var body: some View {
by the argument.
Text("My Number: \(number.formatted(.currency(code: "USD")))")
Text(Date, style: DateStyle)—This initializer creates a Text view to }
}
present a date. The first argument defines the date, and the style 
argument is a structure that determines the format. The structure
includes the type properties date, offset, relative, time, and timer to define Figure 5-19: Text view with formatted values
this value.
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 Of course, we can include and format other types of values. For instance,
include values with string interpolation. we can use the formatted() method on a Date value to show a date.
Listing 5-6: Displaying values with a Text view Listing 5-8: Displaying a date with a Text view


struct ContentView: View {
struct ContentView: View {
let number: Float = 30.87512
let today = 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.
Modifiers

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.
Additionally, Xcode offers a library with a list of views and modifiers that
The Attributes Inspector panel shows the attributes currently assigned to
we can incorporate to our interface by dragging and dropping them onto
the view. In our example, the option Large Title is already selected because
the canvas or the code. The library is activated from the Library button in
we added the font() modifier in Listing 5-10. If we remove the modifier, the
the toolbar (Figure 5-4, number 5), and the lists of views and modifiers are
panel is updated to show the current values. The same happens the other
selected from the buttons at the top of the window. The first button
way around. if we change any value on this panel, the change is reflected
presents a list with all the views we can add to the interface and the
on the canvas and the corresponding modifier is added to the code.
second button opens a list with all the modifiers available. We can also use
Another way to modify our views is with a context menu. If we move the
the search bar at the top of the window for a quick search. Figure 5-24,
mouse over a view on the canvas or in our code and click on it while
below, illustrates how to add a modifier from this library to change the
holding down the Command key, we will see a menu with multiple options
color of the text.
to modify the view. One of those options is called Show SwiftUI Inspector,
which opens a panel with all the same options we can find in the Attributes
Figure 5-24: Adding a modifier from the Library
Inspector panel. Changing the options in this window produces the same
effect as before.
Do It Yourself: Click on the ContentView.swift file and open the properties bottom, bottomLeading, bottomTrailing, center, leading, top,
canvas. Press the Selectable button to activate the Selectable mode topLeading, topTrailing, and trailing.
(Figure 5-22, number 1). Move the mouse to the "Hello World" text padding(CGFloat)—This modifier adds a padding between the
on the canvas, press and hold the Command key and click on it. You view's content and its frame. The argument determines the width of
should see a context menu (Figure 5-23, left). Select the Show the padding. We can provide a CGFloat value to specify the padding for
SwiftUI Inspector option. In the next window, click on the Text field, all sides. (If we do not specify a value, the system assigns a standard
change the text to "Goodbye World", and press Enter. You should see padding that adapts to each device.) The modifier can also take an
the text changing on the canvas and the code. Experiment with EdgeInsets value to specify a padding for each side, or a set of Edge
different values and try to add modifiers to the view from the values along with a CGFloat value to declare which sides we want to
Library, as shown in Figure 5-24. modify and for how much (padding([Edge], CGFloat)). The Edge type is an
enumeration with the values bottom, leading, top, trailing, horizontal (left
Most modifiers are defined by the View protocol and then implemented by and right), and vertical (top and bottom).
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 screen of a device is composed of a grid of hundreds of dots called
the modifiers used to determine the size of the view.
pixels, ordered in rows and columns. The number of pixels varies from one
device to another. To compensate for the disparities between devices,
frame(width: CGFloat?, height: CGFloat?, alignment: Apple adopted the concept of points (sometimes called logical pixels). The
Alignment)—This modifier assigns a new size and alignment to the goal is to have a unit of measurement that is independent of the device
view. The alignment argument determines the alignment of the and the density of the pixels on the screen. A point occupies a square of
view's content. It is a structure of type Alignment with the type one or more pixels, depending on the device. For instance, the screen of an
properties bottom, bottomLeading, bottomTrailing, center, leading, top, iPhone 13 has a grid of 1170 pixels by 2532 pixels, but the views are
topLeading, topTrailing, and trailing. positioned and sized according to the grid in points (390 by 844). Using this
frame(minWidth: CGFloat?, idealWidth: CGFloat?, reference, we can determine the appropriate size of a view for every
device. In the following example, we implement the frame() modifier to give
maxWidth: CGFloat?, minHeight: CGFloat?, idealHeight:
the Text view a size of approximately two thirds the width of the screen
CGFloat?, maxHeight: CGFloat?, alignment: Alignment)—This
(250 points).
modifier assigns a minimum and a maximum size to the view. We can
also define an ideal width and height that will be taken into Listing 5-11: Assigning a fixed size to a view
consideration when the available space must be distributed among 
the views. The alignment argument determines the alignment of the struct ContentView: View {
view's content. It is a structure of type Alignment with the type var body: some View {
Text("Hello World!")
.frame(width: 250, height: 100, alignment: .leading)
}
}
 Listing 5-12: Creating flexible containers

In this example, we take advantage of the alignment argument provided by struct ContentView: View {
var body: some View {
the initializer to align the text to the left. Notice that the value does not Text("Hello World!")
specify the side. Instead, the side is represented by the leading value. There .frame(minWidth: 0, maxWidth: .infinity)
are two values available, leading and trailing. These values represent the left }
}
or the right side of the view depending on the language. For instance, if the 
language set on the device is left-to-right, like English, the leading value is
associated to the left side of the view and the trailing value to the right. 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
Figure 5-25: View with a fixed size and the content aligned to the left container. In our example, we applied this value to the maxWidth
(leading) argument, so the Text view extends to the edges of the screen.
 
Do It Yourself: Update the ContentView view with the code in Listing 5- IMPORTANT: Notice that for a flexible view to work properly it is
11. Press the Selectable button to activate the Selectable mode on recommended to always declare the minimum size as well. If you are
the canvas. Click on the Text view in the code or in the canvas to declaring a flexible width, you should set the minWidth argument,
select it. You should see a blue rectangle indicating the area and if you are declaring a flexible height, you should also include the
occupied by the view, as illustrated in Figure 5-25. minHeight argument.
The arguments in the frame() modifier are optional. We can declare only the Another way to specify a custom size is with the padding() modifier. The
width or the height, and the rest of the arguments will be defined with the padding is inserted between the content and the edges of the view. There
values set by the view. This is particularly useful when we want to turn one are different ways to determine the padding's thickness. For instance, if we
side of the view flexible. Flexible views are defined with the maxWidth and don't specify any value, the system assigns one by default, depending on
maxHeight arguments. For instance, we can apply the maxWidth the device, but we can also provide a CGFloat value to declare a specific
argument to extend the view to the left and right side of the window, and thickness in points.
let the content determine the height.
The code in Listing 5-13 assigns a padding of 25 points to the Text view. The The code in Listing 5-14 assigns padding to specific sides of the view.
padding is applied between the text and the view's frame, as shown below. Another way to achieve the same is with the values of an enumeration
provided by SwiftUI called Edge. This enumeration includes the values
Figure 5-27: View with padding 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.
The View protocol defines a structure called EdgeInsets that we can use to Listing 5-15: Assigning padding with Edge values
specify a width for each side of the view. 
struct ContentView: View {
EdgeInsets(top: CGFloat, leading: CGFloat, bottom: CGFloat, var body: some View {
Text("Hello World")
trailing: CGFloat)—This initializer returns an EdgeInsets structure with .padding([.top, .bottom], 50)
the values specified by the arguments. }
}

The following example implements this structure to assign a padding of 40
points only to the left and right sides. Figure 5-29: Padding at the top and bottom
underline(Bool, color: Color)—This modifier underlines the text. struct ContentView: View {
var body: some View {
The first argument is a Boolean value that indicates whether the text Text("Hello World")
has an underline or not, and the second argument defines the color of .font(.body)
}
the line. }

The body property produces a font similar to the standard font provided by Listing 5-17: Using the system font
the system, but we can use other properties to get fonts with different 
styles and sizes. Figure 5-30, below, shows some of the fonts returned by struct ContentView: View {
these properties compared to the system's standard font. var body: some View {
Text("Hello World")
.font(Font.system(size: 50))
Figure 5-30: Dynamic font types }
}


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
Do It Yourself: Dynamic fonts have a predefine size, but they change
the one defined by the system. If we want to use a custom font, we have to
according to the size set by the user from Settings. If you want to see implement the custom() method.
how your text looks like in different sizes, run the app in the Operating systems come with a set of standard fonts. If the font we want to
simulator or a device, open the Settings app, go to Accessibility / use is already included in the system, we just need to specify its name and
Display & Text Size / Large Text, activate Large Accessibility Sizes, and size, as in the following example.
then change the size from the slider at the bottom.
Listing 5-18: Using standard fonts
Although it is recommended to use dynamic fonts, so the interface 
automatically adapts to the size set by the user, the Font structure also struct ContentView: View {
var body: some View {
includes the following type methods to implement the font defined by the
Text("Hello World")
system or to load custom fonts when necessary. .font(Font.custom("Georgia", size: 50))
}
}
system(size: CGFloat)—This type method returns the system font 
with the size defined by the size argument.
custom(String, size: CGFloat)—This type method returns a font of Figure 5-31: Text with the Georgia font
the type specified by the first argument and with the size specified by
the size argument.

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 If the font we want to include is not provided by the system, we must copy
are not affected by the choices the user makes from Settings. 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.
panel (see Figure 5-13). Every option on the list includes a + button. By
Figure 5-32: Dragging files from Finder to our Xcode’s project 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).
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 In this example, we include the horsepower.ttf file, which defines a font
Copy items if needed, as shown in Figure 5-33 below. We must also indicate called Horse Power. Now, we can use this font from our application, as
that the files are going to be added to the target created for our project shown next.
from the Add to targets option.
Listing 5-19: Using custom fonts
Figure 5-33: Options to add files to the project 
struct ContentView: View {
var body: some View {
Text("Hello World")
.font(Font.custom("Horsepower-Regular", size: 50))
}
}


Figure 5-35: Text with a custom font
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

}

Do It Yourself: Download the horsepower.ttf file from our website or
provide your own. Drag the file from Finder to the project's The structure in Listing 5-20 defines a Text view with a largeTitle font, a
Navigator Area (Figure 5-32). Make sure to check the option "Copy weight of type heavy, a shadow, and underlines the text. In this example, we
items if needed" and the target (Figure 5-33). Go to the app's define the radius, x, and y arguments of the shadow() modifier to 1 to cast a
settings (Figure 5-4, number 6), open the Info panel, click on the + subtle shadow that extends to the right and bottom of the text.
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 Figure 5-36: Multiple styles applied to a text
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. 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.
IMPORTANT: The names we have to provide to the custom() method
are the PostScript names. To find the PostScript name of the font you Listing 5-21: Nesting Text views
want to add to your project, open the Font Book application from 
the Applications folder, go to the View menu, and select the option struct ContentView: View {
Show Font Info. Click on the font. The PostScript name is shown in var body: some View {
Text("Hello \(Text("World").underline())")
the panel on the right. .font(.largeTitle)
}
}
Applying the rest of the modifiers available for Text views is straightforward, 
as shown next.
In this example, we applied the largeTitle and underline modifiers, but because
Listing 5-20: Applying multiple styles to a text the underline modifier was applied to the second Text view, only the text in
 this view is underlined.
struct ContentView: View {
var body: some View { Figure 5-37: Nested Text views with different styles
Text("Hello World")
.font(.largeTitle)
.underline()
.fontWeight(.heavy)
.shadow(color: Color.gray, radius: 1, x: 1, y: 1)
} 
lineLimit(Int)—This modifier determines how many lines the text
The Font structure also includes the following modifiers to style the font. 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
bold()—This modifier adds the bold style to the font. will extend to include the number of lines necessary to show the full
italic()—This modifier adds the italic style to the font. text.
weight(Weight)—This modifier assigns a weight to the font. The multilineTextAlignment(TextAlignment)—This modifier defines
argument is a structure of type Weight with the properties black, bold, the alignment of multiline text. The argument is an enumeration with
heavy, light, medium, regular, semibold, thin, and ultraLight. the values center, leading (default), and trailing.
lineSpacing(CGFloat)—This modifier determines the space
Because these modifiers are defined by the Font structure, they are applied between lines.
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. truncationMode(TruncationMode)—This modifier determines
how the text is truncated when it doesn't fit inside the view's frame.
Listing 5-22: Styling the font The argument is an enumeration with the values head, middle, and tail
 (default).
struct ContentView: View { textSelection(TextSelectability)—This modifier determines if the
var body: some View {
Text("Hello World") text is selectable by the user (the user can copy the text and paste it
.font(.largeTitle.weight(.semibold))
}
somewhere else). The argument is a structure with the type
} properties enabled and disabled.

privacySensitive()—This modifier indicates that the view contains
Figure 5-38: Semibold text 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.
By default, Text views can show multiple lines of text, but we can Listing 5-23: Formatting text
implement modifiers provided by the View protocol to set a limit on the 
number of lines allowed or to format the text. struct ContentView: View {
var body: some View {
Text("Monsters are real, and ghosts are real too. They live inside us, and sometimes, they win.
Stephen King.")
.padding()
.multilineTextAlignment(.center)
.lineSpacing(5)
.textSelection(.enabled)
} 
}

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.
255. For instance, the following initializer assigns an RGB color with the the column on the right displays the content of the selected set, as shown
values 100, 228, 255 (cyan). below.
Listing 5-26: Defining the color with RGB values Figure 5-42: Empty Asset Catalog

struct ContentView: View {
var body: some View {
Color(red: 100/255, green: 228/255, blue: 255/255)
.frame(width: 250, height: 100)
}
}

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 The type properties provided by the Color structure, like the red property
colors adapt to the appearance. The following example creates a Color view implemented in Listing 5-27, define colors that adapt to the appearance
with the red property. In dark mode this color will look slightly different (light or dark). With the Asset Catalog, we can define our own sets of
than in light mode. 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
Listing 5-27: Assigning a dynamic color 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
struct ContentView: View { appearance, and another for the Dark mode.
var body: some View {
Color.red
.frame(width: 250, height: 100)
Figure 5-43: Colors for Light and Dark appearances
}
}

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 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.
Do It Yourself: Click on the Assets item in the Navigator Area to open
Figure 5-44: New color for Dark appearance the Asset Catalog. Click on the + button and select the option Color
Set (Figure 5-42). Click on the set to select it, open the Attributes
Inspector panel and change its name to MyColor (Figure 5-43). Click
on the squares that represent the colors in the Editor Area and set a
different color for each one of them from the Attributes Inspector
panel. Modify the ContentView view with the code in Listing 5-28. To
change the appearance, click on the Device Settings button (Figure 5-
 18, number 4), activate Color Scheme and select the Dark
Appearance option. You should see something like Figure 5-45.
Once the set is defined, we can assign it to a view, as shown next.
The Asset Catalog includes two predefined sets called AccentColor and
Listing 5-28: Assigning a custom color 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
struct ContentView: View { the app.) On the other hand, the AccentColor set defines the color used by
var body: some View {
Color("MyColor")
some views, such as buttons and other controls, to style their content. The
.frame(width: 250, height: 100) 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.
In this example, the Color view will be white in light appearance, but orange Figure 5-46: Accent color
when the appearance is changed to dark.
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
As we already mentioned, the most common application of Color views is to
in a Text view.
define the colors of other views. The following are some of the modifiers
that implement Color views to define the color of the views or are useful
Listing 5-29: Assigning a color to the text of a Text view
when working with colors.

struct ContentView: View {
foregroundColor(Color)—This modifier assigns a color to the var body: some View {
view's content. Text("Hello World")
.font(.largeTitle)
border(Color, width: CGFloat)—This modifier assigns a border to .foregroundColor(Color.red)
}
the view. The first argument determines the border's color and the }
width argument determines its width. 
cornerRadius(CGFloat)—This modifier rounds the corners. The Figure 5-47: Text in different color
argument determines the radius of the corners.
background(View, alignment: Alignment)—This modifier assigns
a view as the view's background. The first argument is a SwiftUI view, 
including Color views, and the alignment argument determines how
the view is going to be aligned within the bounds of the parent view. Besides changing the color of the text, we can assign a color to the view's
foregroundStyle(ShapeStyle)—This modifier assigns a style to the background. The background() modifier can take any view, but it is usually
view's content. The argument is a value that conforms to the ShapeStyle applied with a Color view.
protocol, such as the Color structure for colors, the AngularGradient and
Listing 5-30: Assigning a background color to a Text view
LinearGradient structures for gradients, and the Material structure for

materials.
struct ContentView: View {
var body: some View { modifier creates another view with the characteristics of the
padding()
Text("Hello World")
.font(.largeTitle) previous one but with a padding of 20 points, which expands the view's
.background(Color.gray) 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
Figure 5-48: View with a background applied to the view generated by the font() modifier, which didn't include
the padding, as illustrated below.
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", Figure 5-50: Background and border applied to the view
then the font() modifier creates a new view with a larger font, after that the

struct ContentView: View {
Figure 5-52: Overlay
var body: some View {
Text("Hello World")
.font(.largeTitle)
.padding(20)
.background(Color.gray) 
.cornerRadius(20)
}
} The Color view also includes the following property and method to apply

simple effects to the views.
Figure 5-51: Round corners
gradient—This property applies a gentle gradient to the color.
opacity(Double)—This method defines the color's opacity. The
argument takes values from 0.0 (fully transparent) to 1.0 (fully
 opaque).
The overlay() modifier works like the background() modifier but instead of The gradient property returns a gentle gradient generated from the original
displaying the view in the background, it does so on the front. For instance, color. It can be applied anywhere a Color view is implemented. For instance,
the following code adds a translucent yellow view in front of the view in the following example, we apply it to the background of our Text view.
generated by the previous example.
Listing 5-35: Applying a predefined gradient to a view
Listing 5-34: Displaying a view in front of another view 
 struct ContentView: View {
var body: some View {
Text("Hello World") Materials
.font(.largeTitle)
.padding(20)
.background(Color.gray.gradient)

.cornerRadius(20)
}
}
Modern interfaces make extensive use of blur effects and transparency.
 Although we can set the opacity of a Color view to make it translucent, as
we did in the example of Listing 5-34, the SwiftUI framework offers a better
Figure 5-53: Gradient alternative with Materials. Materials apply a blur effect to the background
of a view, producing an effect that resembles frosted glass. SwiftUI includes
the Material structure to create these materials, and this structure defines
the following type properties to produce standard effects: ultraThinMaterial,
 thinMaterial, regularMaterial, thickMaterial, and ultraThickMaterial.
Materials are applied to the view with the background() modifier. The
translucent effect they produce is useful when the view appears on top of
other views (they overlap), but we can test it with a single view, as shown
next.
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.
Images


Images are used for everything in modern applications, from backgrounds
and patterns to the creation of customized controls. But before
Materials can also be applied to the view's content with the foregroundStyle()
incorporating images into our projects, we must consider that they are
modifier, as in the following example.
stored in files with a resolution in pixels, while the user interface is defined
in points. As we already mentioned, the screens of Apple devices have
Listing 5-37: Applying a material to the view's content different resolutions and scales. In some devices, one point represents one

pixel and in others more. At this moment, three scales have been defined:
struct ContentView: View {
var body: some View { 1x, 2x, and 3x. The 1x scale defines one point as one pixel, the 2x scale
Text("Hello World") defines 1 point as a square of 2 pixels, and the 3x scale defines one point
.font(.largeTitle)
.background(.red) as a square of three pixels. For this reason, every time we want to show
.foregroundStyle(.thickMaterial) images in our interface, we must consider the conversion between pixels
}
} and points. For example, if we have an image of 300 pixels wide and 400
 pixels tall, in a device with a scale of 1x the image will almost fill the
screen, but in a device with a scale of 2x the image will look half its size.
Figure 5-55: Material applied to the text The image is occupying the same space, 300 by 400 pixels, but because of
the higher resolution the pixels represent a smaller area on the screen in
devices with scales of 2x or 3x, as shown below.

One solution to this problem is to scale up a small image in devices with the scale that corresponds to the image by reading a suffix on the file’s
higher resolution or scale down a big image in devices with lower name. What we need to do is to provide three files with the same name
resolution. For example, we can expand an image of 300 x 400 pixels to but with suffixes that determine the scale for which they were designed.
600 x 800 pixels and make it look like the same size in a screen with a scale The file containing the image for the 1x scale (300 x 400 pixels in our
of 2x (a space of 300 x 400 points represents 600 x 800 pixels at this scale), example) only requires the name and the extension (e.g., husky.png), the
or we could start with an image of 600 x 800 pixels and reduce it to 300 x name of the file with the image for the 2x scale (600 x 800) must include
400 pixels for devices with half the scale. One way or another, we have a the suffix @2x (e.g., [email protected]), and the name of the file with the
problem. If we expand a small image to fill the screen, it loses quality, and image for the 3x scale (900 x 1200) must include the suffix @3x (e.g.,
if we reduce it, it occupies unnecessary space in memory because the [email protected]). Every time the interface requires an image, the system
image is never shown in its original resolution. Fortunately, there is a more reads the suffixes and loads the one corresponding to the scale of the
efficient solution. It requires us to include in our project three versions of screen.
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 IMPORTANT: There is a useful application in the App Store for Mac
for devices with a scale of 1x, another of 600 x 800 pixels for devices with a computers called Prepo that can take an image of a scale of 3x and
scale of 2x, and a third one of 900 x 1200 for devices with a scale of 3x. reduce it to create the versions for the rest of the scales. It can also
Now, the images can be shown in the same size and with the same quality help you generate the icons for your app. (We will learn more about
no matter the device or the scale. icons in Chapter 20.)
Figure 5-57: Different images for specific scales 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
Providing the same image in different resolutions solves the problem but empty set is created.
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 Figure 5-58: New set of images
scale of the device. To help us select the right image, Apple systems detect
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.

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 The image is independent of the view. If we resize the view with the frame()
image we put in the Asset Catalog called Toronto. 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.
Listing 5-38: Displaying an image

clipped()—This modifier clips the image to the view's frame.
struct ContentView: View {
var body: some View { resizable()—This modifier resizes the image to fit within the view's
Image("Toronto")
}
frame.
} aspectRatio(CGSize, contentMode: ContentMode)—This

modifier changes the image's aspect ratio to the values specified by
Do It Yourself: Download the Toronto image from our website or the first argument and resizes the image according to the mode
provide your own. Drag the files to the Asset Catalog in your project. specified by the contentMode argument. This argument is an
enumeration of type ContentMode with the values fill and fit. If we want
to use the original aspect ratio, we can declare the first argument as This reduces the size of the visible image, but the image is still presented in
nil or just ignore it. 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
scaledToFit()—This modifier scales the image to fit within the view.
the resizable() modifier.
It works like the aspectRatio() modifier with the aspect ratio set to nil
and the mode to fit.
Listing 5-40: Resizing the image
scaledToFill()—This modifier scales the image to fill the view. It 
works like the aspectRatio() modifier with the aspect ratio set to nil and struct ContentView: View {
var body: some View {
the mode to fill.
Image("Toronto")
.resizable()
There are several transformations we can apply to the image with these .frame(width: 250, height: 100)
}
modifiers. One alternative is to clip the image to the view's frame with the }
clipped() modifier. 
Listing 5-39: Clipping the image The resizable() modifier creates a view that adjusts the size of the image to fit
 the space available within the view's frame.
struct ContentView: View {
var body: some View { Figure 5-63: Image resized
Image("Toronto")
.frame(width: 250, height: 100)
.clipped()
}
}


The clipped() modifier creates a new view that only shows the part of the
image that is within the view's frame. IMPORTANT: As we already mentioned, modifiers return a new view
and therefore the order in which they are applied matters. The
Figure 5-62: Image clipped resizable() modifier applied in the example of Listing 5-40 is
implemented by the Image structure and therefore it must be applied
first. If we try to declared this modifier after the frame() modifier, we
will get an error because the frame() modifier does not return an Image
view, it returns a view of type some View.

In the previous example, we didn't have to clip the image because the There are two modes available: fit and fill (scaledToFit() and scaledToFill())
resizable() modifier resizes the image to the size of the view. But this creates Figure 5-64, below, shows what happens when we apply these content
another problem. The image is squashed. If we want to resize the image modes to the image of our example.
but keeping its original aspect ratio, we must define the content mode with
the aspectRatio() modifier. The values available are fit (Aspect Fit) and fill Figure 5-64: Aspect fit (left) and aspect fill (right)
(Aspect Fill).
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.
Of course, we can also apply common modifiers to an Image view. Some
modify the view, others the image. The following example adds a padding,
Listing 5-44: Resizing image to fill the container
round corners, and a shadow to our image.

struct ContentView: View {
var body: some View { Listing 5-45: Applying style modifiers to an Image view
Image("Toronto") 
.resizable()
.scaledToFit() struct ContentView: View {
var body: some View {
}
} Image("Toronto")
 .resizable()
.scaledToFit()
.cornerRadius(25)
When the size of the frame is not declared, the view works along with the .padding(20)
.shadow(color: Color.black, radius: 5, x: 5, y: 5)
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. Again, the order of the modifiers is important. In this example, we applied
the padding() after the cornerRadius() to make sure the rounded corners are
Figure 5-66: Flexible Image view applied to the image, not the padding.
blur(radius: CGFloat, opaque: Bool)—This modifier applies a blur The size of the view remains the same, but the size of the image is reduced
effect to the view. The radius argument determines how diffuse the by half with the scaleEffect() modifier.
blur effect is, and the opaque argument determines whether the blur
effect is going to be opaque or transparent. Figure 5-68: Visual effects applied to an image
colorMultiply(Color)—This modifier multiplies the view's colors
with a specific color. As a result, the view's original colors tend toward
the color defined by the argument.
saturation(Double)—This modifier increases or decreases the
intensity of the colors.
contrast(Double)—This modifier applies contrast to the view.

opacity(Double)—This modifier defines the view's opacity. The
argument takes values from 0.0 (fully transparent) to 1.0 (fully In addition to the modifiers available to specify the size of the view and
opaque). scale the image, we can also adapt it to the font size selected by the user
scaleEffect(CGSize)—This modifier changes the horizontal and from Settings, as we did before with Dynamic fonts (see Listing 5-16). For
this purpose, SwiftUI includes the following property wrapper.
vertical scales of the view to the values specified by the argument.
The modifier only affects the view's content.
@ScaledMetric(relativeTo: TextStyle)—This property wrapper
scales a value according to the font size selected by the user from the
The implementation of these modifiers is straightforward. The following
example scales the image to half its size and makes it blurry. Settings app. The relativeTo argument is an enumeration value that
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).
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 image is created as before, but now the modifier determines the
var body: some View {
Image(systemName: "envelope") symbol's variant.
.font(myfont.weight(.semibold))
}
}
Listing 5-51: Assigning a variant to a symbol
 
struct ContentView: View {
The code in Listing 5-50 initializes a Font structure with the system's font var body: some View {
Image(systemName: "envelope")
and a size of 100, and then modifies an Image view with this font and a .font(Font.system(size: 100))
weight of type semibold. This is the same as before, but makes our code .symbolVariant(.fill)
}
easier to read. }
No matter how our code is organized, the view always shows the symbol of 
an envelope on the screen.
This is the same as creating the Image view with the "envelope.fill" string,
Figure 5-71: SF Symbol 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 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 SF Symbols are displayed in the color and size of the font, but some
in the SF Symbol application, but the framework also includes the following symbols can include up to three more colors, and all of them can be scaled
modifier to specify a variant. up or down. SwiftUI includes the following modifiers for this purpose.
attribute. The attribute is an enumeration with the values small, foregroundStyle() modifier, as in the following example.
image attribute.
Listing 5-54: Displaying a variable SF Symbol labelStyle(LabelStyle)—This modifier configures the label. The

argument is a structure that conforms to the LabelStyle protocol, which
struct ContentView: View {
var body: some View { defines type properties to tell the label what to include. The
Image(systemName: "dot.radiowaves.forward", variableValue: 0.8) properties available are automatic (default), iconOnly, titleAndIcon, and
.font(.largeTitle)
} titleOnly.
}

A Label view determines the text and the image to be shown, but we can
The value of the variableValue argument must be below the threshold for use the font() modifier to specify the characteristics of the font, as shown
each state. The symbol implemented in the view of Listing 5-54 have 5 next.
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 Listing 5-55: Displaying a text with an SF Symbol
possible values for this symbol and all the states it can take. 
struct ContentView: View {
var body: some View {
Figure 5-75: Variable SF Symbol Label("Hello", systemImage: "envelope.circle")
.font(.largeTitle)
.labelStyle(.titleAndIcon)
.imageScale(.large)
}
 }

Although we can combine Text views with symbols to build the interface, as Notice that in this example we implement the imageScale() modifier to make
we will see later, SwiftUI includes a view called Label to show a text along the symbol slightly larger than the text. The result is shown below.
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 Figure 5-76: Label with an SF Symbol
the following initializers and modifier to create these views.
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.
Custom Modifiers MyModifiersthat then we can apply to the views in our interface. The result
is the same as applying the modifiers directly to the views, but it makes our

code less repetitive.
Interfaces with single views, like those we have created so far, are the
Figure 5-77: Custom modifiers applied to a view
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 As any other structure, the ViewModifier structure can include properties,
parameter of type Content. The parameter represents the views we want to and those properties may get different values every time the modifier is
modify and, therefore, it is to this parameter that we apply the actual applied. For instance, we can include a property to store a CGFloat value, so
modifiers, as shown next. every time the custom modifier is applied, we can select the size to be
assigned to the font.
Listing 5-57: Applying custom modifiers
 Listing 5-58: Customizing a custom modifier
import SwiftUI 
struct MyModifiers: ViewModifier {
struct MyModifiers: ViewModifier {
var size: CGFloat
func body(content: Content) -> some View {
content
init(size: CGFloat) {
.font(Font.system(size: 100).weight(.semibold))
self.size = size
.foregroundColor(Color.blue)
}
}
func body(content: Content) -> some View {
}
content
struct ContentView: View {
.font(Font.system(size: size).weight(.semibold))
var body: some View {
.foregroundColor(Color.blue)
Image(systemName: "envelope.circle")
}
.modifier(MyModifiers())
}
}
struct ContentView: View {
}
var body: some View {

Image(systemName: "envelope.circle")
.modifier(MyModifiers(size: 50))
In this example, we define a structure that conforms to the ViewModifier }
}
protocol and then apply the font() and foregroundColor() modifiers to the 
content parameter of the body method. This defines a custom modifier called
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 Stacks are created with values by default. For instance, a VStack aligns its
views to the center and with a standard space in between. If that's all we

need, we just have to declare its content.
}
}
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 Figure 5-82: Views in a ZStack
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.
If we ignore the alignment argument, the views are aligned to the center,
Figure 5-81: HStack alignments but there are other alignments available. The values are bottom,
bottomLeading, bottomTrailing, center, leading, top, topLeading, topTrailing, and trailing.
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.
When views overlap, like those included in a ZStack view, the system
Listing 5-63: Creating a ZStack 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
struct ContentView: View { front of it, and so on. The View protocol defines the following modifier to
var body: some View { change this order.
ZStack(alignment: .center) {
Image(systemName: "cloud")
.font(.system(size: 80)) zIndex(Double)—This modifier sets the order of the view in the Z
Text("New York")
.font(.body.bold()) axis (the axis perpendicular to the screen).
.foregroundColor(.gray)
}
By default, all the views are assigned the index 0, which means they are all When we group views with a stack, we can apply modifiers to all the views
at the same level, and that is why the system draws the views according to at the same time by assigning the modifiers to the stack instead of the
the order in which they are declared in the code, but we can move a view individual views. For instance, if we want to assign the same color to the
to the back by assigning a negative Z index or to the front with a value Text view and the Image view of the previous example, we can apply the
greater than 0. When views have different indexes, they are drawn in the foregroundColor() modifier to the ZStack view.
order determined by those values, starting from the view with the smallest
index. In the following example, we include the same Image view and Text Listing 5-65: Assigning modifiers to the stack and its content
view used before, but because we set an index of -1 for the Text view, it is 
drawn in the back. struct ContentView: View {
var body: some View {
ZStack(alignment: .center) {
Listing 5-64: Setting the Z index of a view Image(systemName: "cloud")
 .font(.system(size: 80))
Text("New York")
struct ContentView: View { .font(.body.bold())
var body: some View { }.foregroundColor(Color.red)
ZStack {
}
Image(systemName: "cloud")
}
.font(.system(size: 80))

Text("New York")
.padding(8)
.background(Color.yellow) Figure 5-85: Modifier applied to container
.zIndex(-1)
}
}
}
 
The Text view in Listing 5-64 includes a yellow background, so we can see its
Stacks can be combined and nested as required by the interface. For
position in the Z axis. The Image view should be drawn first, and then the
instance, we can incorporate a VStack inside the previous HStack to include
Text view should cover most of the image, but because we set an index of
more text next to the image.
-1 for the Text view, it is drawn in the back.
Listing 5-66: Nesting stacks
Figure 5-84: Custom Z index

struct ContentView: View {
var body: some View {
HStack {
 Image(systemName: "cloud")
.font(.system(size: 80))
VStack(alignment: .leading) {
Text("City") the minimum size in points the space can take. If the argument is not
.foregroundColor(.gray)
Text("New York") declared, the minimum length is 0.
.font(.title)
}
} A Spacer view can be implemented any time we need to add a flexible
} space. It provides a more customizable way to align the views or extend
}

them to the sides of its container. For instance, in the following example
we add a flexible space between the image and the VStack of our view to
The code in Listing 5-66 defines a Vstack view inside an HStack view. The move the views to the left and right side of their container.
VStack includes two Text views aligned to the left and with different styles.
Listing 5-67: Aligning the views with a flexible space
Figure 5-86: Nested stacks 
struct ContentView: View {
var body: some View {
HStack {
Image(systemName: "cloud")
.font(.system(size: 80))
 Spacer()
VStack(alignment: .leading) {
Text("City")
IMPORTANT: When you decide to include a view in a stack, you can .foregroundColor(.gray)
Text("New York")
write the code yourself or get Xcode to do it for you by selecting the .font(.title)
option from the context menu. We used this menu before to add }
}
modifiers to a Text view (Figure 5-23). If you press the Command key }
and click on the view, the context menu appears with the options to }
embed the view in a stack. 
The system calculates the widths of the image and the stack, and then
The alignment options available for VStack and HStack views align the views
assigns the rest of the space available to the Spacer view in the middle.
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
Figure 5-87: Flexible space between views
the same axis, we must add a flexible space. SwiftUI includes the Spacer
view for this purpose.
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.
Safe Area

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.

ignoresSafeArea(SafeAreaRegions, edges: Edge)—This modifier
For instance, if we want the interface of our previous example to always
expands the view outside the safe area. The first argument
extend to the edges of the screen, we can apply the modifier to the VStack
determines the safe areas that are ignored. It is a structure with the view with the value all.
type properties all, container, and keyboard. The edges argument is a
value or a set of values that indicates the sides to be ignored. This is Listing 5-69: Ignoring the safe area
an enumeration with the values all, bottom, leading, top, and trailing. 
struct ContentView: View {
There are two safe areas, one called container that determines the space var body: some View {
VStack {
available inside the window after all the navigation bars and toolbars are HStack {
displayed, and another called keyboard that determines the remaining space Image(systemName: "cloud")
.font(.system(size: 80))
after the virtual keyboard becomes visible. VStack(alignment: .leading) {
Text("City")
.foregroundColor(.gray)
Figure 5-89: Safe areas Text("New York")
.font(.title)
}
Spacer()
}
Spacer()
}.ignoresSafeArea(.all)
}
}
 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
Figure 5-90: Views ignoring the safe area keyboard, the views will move up to remain 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 SwiftUI includes a modifier to expand the safe area. The modifier changes
doesn't overlap the views. the safe area inset to include additional space.
Listing 5-70: Ignoring only the container safe area at the bottom safeAreaInset(edge: Axis, alignment: Alignment, spacing:

CGFloat?, content: Closure)—This modifier expands the safe area
struct ContentView: View {
var body: some View { with a custom view. The edge argument determines the side we want
VStack { to modify. It is specified with the values leading and trailing, provided by
Spacer()
HStack { the HorizontalEdge enumeration, or the values bottom and top, provided
Image(systemName: "cloud")
.font(.system(size: 80))
by the VerticalEdge enumeration. The alignment argument determines
VStack(alignment: .leading) { the alignment of the views inside the area. It is specified with the type
Text("City")
.foregroundColor(.gray) properties center, leading, and trailing, provided by the HorizontalAlignment
Text("New York") structure, or the type properties bottom, center, firstTextBaseline,
.font(.title)
} lastTextBaseline, and top, provided by the VerticalAlignment structure. The
Spacer()
}
spacing argument determines the space between the views inside the
}.ignoresSafeArea(.container, edges: .bottom) area. And finally, the content argument is a closure that returns the
} views to show inside the area.
}

This modifier may be used to make sure that important views at the edge
in this example, we move the Spacer view to the top of the vertical stack to of the screen are always visible, or to create our own navigation bars, as
push the content down. We ignore the safe area again, so the content is shown next.
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.
 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. its content and the "Manchester" text will be shown in full, regardless of
the priority of the rest of the views.
Figure 5-94: Custom priorities
Figure 5-95: View with a fixed size


When we assign a higher priority to a view and there is no space to show
them all, the system lays out the views with lower priority first, gives them
the minimum possible size, and then assigns the remaining space to the
view with the higher priority. So, if there is still no room to show the entire
view, its content is clipped. If what we want is to force the view to take the
size of its content no matter what, we must apply the fixedSize() modifier.
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
Alignment Guides Image("signphone")
}.border(Color.blue, width: 2)
 }
}

There are two possible alignments, horizontal and vertical. Horizontal
stacks align the views vertically and vertical stacks align the views A stack view determines a common point of alignment according to its
horizontally. This is because SwiftUI expects the views in the stack to be of alignment type and the dimensions of the views. For instance, if the
different sizes and therefore it needs to know how they are going to be alignment of a vertical stack is center, the stack gets the position of the center
aligned in the perpendicular axis. Usually, this is enough to build a simple alignment of each view and from that value it calculates a common point
interface, but professional applications require more control. The View of alignment (usually the point of alignment of its tallest view) and then
protocol defines the following modifiers to customize alignment. repositions all the views to match that common point.
The following example aligns three images of different sizes. They are all Figure 5-96 shows our three images aligned to the center. In this example,
100 points wide, but the signbus image is 200 points tall, the signplane we applied a blue border to the stack to make it easy to see the changes
image is 170 points tall, and the signphone image is 220 points tall. (The produced by the alignment and added a red line on top of the picture to
images are available on our website.) visualize the common point of alignment chose by the stack.
Listing 5-75: Aligning images to the center with standard values Do It Yourself: Download the signbus, signplane and signphone
 images from our website and add them to the Asset Catalog. Update
struct ContentView: View { the ContentView view with the code in Listing 5-75. You should see
var body: some View {
HStack(alignment: .center) { something similar to Figure 5-96.
Image("signbus")
Image("signplane")
The alignment values are determined by the stack's coordinates system, HStack(alignment: .center) {
Image("signbus")
starting from the top-left corner. The value at the top is 0 and the value at .alignmentGuide(VerticalAlignment.center, computeValue: { dimension in
the bottom of the view depends on the children's height. Every view has an return dimension[VerticalAlignment.center] + 18
})
alignment guide with values that determine their points of alignment. The
Image("signplane")
value associated with the top alignment is 0, the value associated with the Image("signphone")
bottom alignment is equal to the view's height, and the value associated }.border(Color.blue, width: 2)
}
with the center alignment is the height divided by two (the formula is: top + }
(bottom - top) / 2). 
The views return these alignment guides by default, but we can change
them with the modifier introduced above. For instance, the wheels of the The alignmentGuide() modifier requires two values. The first one is a value
bus in our example are below the common point of alignment (see Figure that represents the type of alignment we want to modify. In this case, we
5-96, above). This image is 200 points tall, so the center alignment are aligning the views to the center, so we modify the VerticalAlignment.center
returned by the image is at 100 points, but the wheels are 18 points below. 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
Figure 5-97: Alignment guides for the bus 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.

Figure 5-98: Bus aligned with custom values
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.
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 } So far, we have modified the alignment guides of views that belong to the
Image("signphone") same stack. If our interface requires us to align views from different
.alignmentGuide(VerticalAlignment.center) { dimension in
dimension[VerticalAlignment.center] + 89 } containers (stacks), we must define custom alignment types. Custom
}.border(Color.blue, width: 2) 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.
The code in Listing 5-77 declares the closures as trailing closures and omits For this purpose, the extension must include an enumeration that
the return keyword to simplify the code, but the process is the same. The conforms to the AlignmentID protocol, which requires the implementation of
image of the bus is 200 points tall, its default center is at 100 points, but a method called defaultValue to return the alignment's default value. The
the base of the bus is at 118 points, so we add 18 points to the current extension must also include a type property which sole purpose is to
center alignment (118 - 100). The image of the plane is 170 points tall, its simplify the declaration of the alignment, as shown in the following
default center is at 85 points, but the base of the plane is at 153 points, so example.
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 Listing 5-78: Defining custom alignment guides
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 import SwiftUI
aligned by the graphic's baseline.
extension VerticalAlignment {
enum BusAlignment: AlignmentID {
Figure 5-99: Images aligned by the baselines 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) The red line drawn in front of the picture in Figure 5-100 shows the
}
}.border(Color.blue, width: 2) 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
This code defines an extension for the VerticalAlignment structure. We call the guides of the views. For instance, if we want to position the "Bus" text in
enumeration BusAlignment because we use it to align the image of the bus. line with the bus's window, we must move the alignBus alignment for those
Its default value was defined as the current value of the center alignment. views. The alignment for the bus image has to be at the center of the bus's
After this, we define a type property called alignBus that returns an window and the alignment for the text has to be at the center of the word.
alignment of this type. Notice that the value provided to the
VerticalAlignment's initializer is a reference to the definition of the BusAlignment Figure 5-101: Alignments required for the views
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 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.
Groups Each Group view defined in Listing 5-80 contains two Text views. To style
these views, we apply modifiers to the Group views, not their content, so all

the views within a group are affected by the same modifier.


If what we want is for a cell to occupy two or more rows, we need to
embed a grid within another grid, as shown next.
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.
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...)
Once we select the Extract Subview option, a new view is created and or by pressing Command + N. Xcode offers two templates to create Swift
placed at the bottom of the file. The view is assigned the name files, one for common Swift files, used to store Swift code, and another for
ExtractedView, but we can change it to one that better represents our view. SwiftUI views. We can find them in the iOS tab, under the names Swift File
and SwiftUI View.
Listing 5-85: Extracting views
 Figure 5-108: Swift files
import SwiftUI
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.
Although they have different names, they are both Swift files and are sizeThatFits(proposal: ProposedViewSize, subviews:
created with the same extension (.swift). The only difference between the Subviews, cache: Cache)—This method is called on the layout
two is the code included by Xcode. A Swift file only includes an import structure when the system needs to know the size of the container
statement for the Foundation framework, while a SwiftUI View file includes a view. The method must calculate the size and return a CGSize value
View structure with a simple view inside and a PreviewProvider structure to
with the container's width and height. The proposal argument is a
create the preview on the canvas. The View structure takes the name structure that determines the proposed size for the container. The
assigned to the file. For instance, if we want to create a SwiftUI View file to
structure defines three type properties to return a proposal: zero,
store the ExtractedView structure from the previous example, we must call
infinity, and unspecified. The subviews argument is a collection of
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 structures that represent each view in the container. And the cache
structure's name.) Notice that if we store a view in a separate file, we must argument is a storage space to share the calculated values between
import the SwiftUI framework again or the SwiftUI views won't be methods.
recognized. placeSubviews(bounds: CGRect, proposal: ProposedViewSize,
subviews: Subviews, cache: Cache)—This method is called on the
layout structure when the system needs to know the position of each
view inside the container. The bounds argument is the bounds of the
container. The proposal argument is a structure that determines the
proposed size for the container. The structure defines three type
properties to return a proposal: zero, infinity, and unspecified. The
subviews argument is a collection of structures that represent each
These structures work like the VStack and HStack structures, but they
conform to the Layout protocol and are therefore used to define the
interface's layout. For instance, we can use a VStackLayout structure to
replace our custom layout when the value of a state property changes. To
switch between layouts, we need to assign them to a property, as shown
next.

Listing 5-87: Selecting a layout
Although we can present different interfaces with conditional statements, 
this requires the system to recreate the views. SwiftUI includes the struct ContentView: View {
AnyLayout structure to create a wrapper that makes it easy to swap layouts @State private var selected: Bool = true
without recreating the views. As we will see later, this allows us to
var body: some View {
implement complex features such as view identification and animations. let SelectedLayout = selected ? AnyLayout(MyLayout()) :
To show how we can switch layouts with the AnyLayout structure, we are AnyLayout(VStackLayout(alignment: .leading))
going to use the custom layout defined in the previous example along with
VStack(alignment: .leading) {
layouts provided by the system. SwiftUI includes the following structures to Toggle(isOn: $selected, label: {
define horizontal and vertical layouts. Text(selected ? "Custom" : "Standard")
}).padding(.bottom)
SelectedLayout {
VStackLayout(alignment: HorizontalAlignment, spacing: Group {
Text("First")
CGFloat?)—This structure creates a layout that arranges a group of .padding(10)
views vertically. The alignment argument determines the horizontal .background(.red)
.cornerRadius(10)
alignment of the views. It is a structure with the type properties center, Text("Second")
leading, and trailing. And the spacing argument determines the space .padding(10)
.background(.red)
between the views. .cornerRadius(10)
Text("Third")
HStackLayout(alignment: VerticalAlignment, spacing: .padding(10)
.background(.red)
CGFloat?)—This structure creates a layout that arranges a group of .cornerRadius(10)
views horizontally. The alignment argument determines the vertical }
}
alignment of the views. It is a structure with the type properties Spacer()
bottom, center, firstTextBaseline, lastTextBaseline, and top. And the spacing }.padding()
.font(.title)
argument determines the space between the views }
}

This code defines a State property of type Bool and adds a Toggle button to Generic Views
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.
Views are of different data types, so if we want to pass different views
around from a property or a method, we must wrap them in container
Figure 5-110: Custom and Standard layouts
views, such as the Group view introduced above. Although this is allowed,
SwiftUI includes a structure called AnyView that we can use for that
purpose.
Do It Yourself: Replace the code in your ContentView.swift file with In the following example, we check a condition in a method and return an
the code in Listing 5-86. Remember to keep the ContentView_Previews AnyView view with the content for the body property.
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() The code in Listing 5-89 applies the @ViewBuilder property wrapper to the
that returns a structure of type AnyView. This structure is defined according getView() method, so now we can use this method as a Content closure and
to the value of a Boolean property. If the value is true, we create an Image produce any type of view we want without using a wrapper. (We will learn
view, otherwise, we create a Text view, but both views are inside an AnyView more about property wrappers in Chapter 6.)
view, so we always return the same type of view. When the content of the When the views returned by a method are dynamically selected, as we did
body property is processed, the method is called, and the view returned by in the previous examples, we may not always be able to provide one. For
the method is displayed on the screen. cases like this, SwiftUI includes the EmptyView view.
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 EmptyView()—This initializer creates an EmptyView view with no
views wrapped in an AnyView view to be the same, which affects content and no size.
performance. A better alternative is processing the views before they are
returned by the method with the @ViewBuilder property wrapper. We The EmptyView view works like any other, but it doesn't provide any content
introduced this property wrapper before. It is used to construct the views and it has no size, so it doesn't affect the interface. The following example
returned by Content closures (e.g., the closures assigned to the body creates an empty view when a condition is not met.
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 Listing 5-90: Returning an empty view
wrapper, as in the following example. 
struct ContentView: View {
Listing 5-89: Constructing views with the @ViewBuilder property wrapper var body: some View {
VStack {
 Text("View Title")
struct ContentView: View { getView()
var body: some View { }
getView() }
} @ViewBuilder
@ViewBuilder func getView() -> some View {
let valid = false
func getView() -> some View {
let valid = false
if valid {
Image(systemName: "keyboard")
if valid { } else {
Image(systemName: "keyboard") EmptyView()
} else { }
Text("The state is invalid") }
} }
} 
}

5.4 Previews Preview Modifiers
 
Xcode automatically builds the app and shows on the canvas a preview of Every SwiftUI view includes a PreviewProvider structure to create the preview.
the view we are currently working on. If we introduce large changes to the The structure creates an instance of the view, so we can see on the canvas
code, we must press the Resume button at top of the canvas to tell Xcode everything the user will see when running the app. This is useful enough,
to resume the preview, but otherwise the process is automatic. but by default we only get to see our interface in one device, and a specific
configuration. To adapt the preview to our needs, the View protocol defines
the following 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. devices available, open the Terminal app and insert the command
xcrun simctl list devicetypes.
Listing 5-91: Configuring the preview
 Besides representing a device, a preview can have a free form. The
struct ContentView_Previews: PreviewProvider { previewLayout() modifier uses a value of the PreviewLayout enumeration to
static var previews: some View {
ContentView() determine the preview's type. The value by default is device, which
.previewDevice(PreviewDevice(stringLiteral: "iPhone 13")) configures the preview to represent a device, but there are two more. The
}
fixed(width: CGFloat, height: CGFloat) value specifies a fixed size, and the
}
 sizeThatFits value adapts the size of the preview to the size of the view. For
instance, the following example applies the sizeThatFits value to the preview
This example configures the preview to represent an iPhone 13, but we can of the ContentView view created by the template.
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

Listing 5-92: Configuring the preview to represent multiple devices struct ContentView_Previews: PreviewProvider {
 static var previews: some View {
ContentView()
struct ContentView_Previews: PreviewProvider { .previewLayout(.sizeThatFits)
static var previews: some View { }
Group { }
ContentView() 
.previewDevice(PreviewDevice(stringLiteral: "iPhone 13"))
.previewDisplayName("iPhone 13")
ContentView() Figure 5-111: Preview of the size of the view
.previewDevice(PreviewDevice(stringLiteral: "iPhone 13 Pro"))
.previewDisplayName("iPhone 13 Pro")
}
}
}
 
This example also includes the previewDisplayName() modifier to define the
label for the buttons displayed at the top of the canvas to select the device.
contains information about the application and the views. It is like an font—This property sets or returns the font by default. It is a value of
external storage space accessible from anywhere in our code. The values type Font.
stored in the environment can be modified or more data can be added to
accessibilityEnabled—This property sets or returns a Boolean value
it. Because of its characteristics, the environment is also used to provide
that determines whether any accessibility service has been enabled
access to the user's data, as we will see in the next chapter, or to access
on the device.
databases and files from anywhere in the application. But we can also
modify its values to change the configuration of a view to simulate layoutDirection—This property sets or returns the layout's
different conditions for previews, such as setting the light and dark direction. It is an enumeration of type LayoutDirection with the values
appearances or the font type. leftToRight and rightToLeft.
The environment stores the configuration for the views in properties, so calendar—This property sets or returns the calendar used by the
changing the values of these properties affects how the views look or
views to process dates. It is a value of type Calendar.
behave. The following is the modifier defined by the View protocol for this
purpose. locale—This property sets or returns the locale used by the views to
process local data, such as language, currency, etc. It is a value of type
environment(KeyPath, Value)—This modifier processes the view Locale.
and returns a new one with the characteristics defined by the timeZone—This property sets or returns the time zone used by the
arguments. The first argument is a key path to the environment views to calculate dates and times. It is a value of type TimeZone.
property we want to modify, and the second argument is the value we
want to assign to that property. The implementation is simple. We must apply the environment() modifier to
the view with the key path of the property we want to modify and provide
Environment properties are defined in a structure called EnvironmentValues. the new value. The key path is declared as always (see Chapter 3, Listing 3-
Some of these properties are used to control the behavior of the 32), but this time we can omit the data type because Swift can infer it, as in
application and the views, but others are useful when working with the following example.
previews, as shown below.
Listing 5-94: Activating dark mode
colorScheme—This property sets or returns the interface 
appearance. It is an enumeration of type ColorScheme with the values struct ContentView: View {
var body: some View {
light and dark. Text("Hello World")
Do It Yourself: Create a Color Set in the Asset Catalog with the name In this example, we also apply the previewLayout() modifier to adjust the
MyColor and assign different colors for the Any and Dark views to the size of their content. Figure 5-112, below, shows the previews
appearances, as we did in the example of Figure 5-44. Update the generated by this code.
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 Figure 5-112: Multiple previews with different environment configurations
appearance. Remove the environment() modifier and resume the
preview. Now, you should see the text in the color selected for Any.

If we need to compare configurations, we can group multiple views with a
Group view and modify each one independently. The following example
tests three different sizes for the text. The dynamicTypeSize property is where Do It Yourself: Replace the code in your ContentView.swift file with
the environment stores the value that represents the size for the text the code in Listing 5-95. Assign other values to the dynamicTypeSize
selected by the user from Settings. By modifying this property, we can see property to see what the text looks like. We will see how to
what our interface looks like when the user selects different sizes of text. implement additional environment properties in practical situations
in further chapters.
CHAPTER 6 - DECLARATIVE USER INTERFACE
6.1 States 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
In the previous chapter, we introduced SwiftUI's main feature, its therefore the interface is updated to reflect it. Establishing this
declarative syntax. With SwiftUI we can declare the views the way we want dependency between the app's data and the interface demands a lot of
them to appear on the screen and let the system take care of creating the code, but SwiftUI keeps it simple using property wrappers.
code necessary to make it happen. But a declarative syntax is not just
about organizing the views, it is also about how they are updated when the
state of the app changes. For instance, we may have an interface like the
one in Figure 6-1, below, with a Text view displaying a title, an input field for
the user to insert a new title, and a button to replace the old title with the
new one.
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.
Property Wrappers }
}
 }
init(wrappedValue: Int) {
self.wrappedValue = wrappedValue
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 Listing 6-1 defines a property wrapper called ClampedValue. The structure
our code. For instance, we can define a property wrapper that limits the contains three properties to store and control the value. The storedValue
value of a property to a certain range. All the properties declared with it property stores the current value of the property, and the min and max
will only accept values between those limits. properties determine the minimum and maximum values allowed for the
A property wrapper is just a structure, but it must be preceded by the properties defined with this property wrapper. There is also the required
@propertyWrapper keyword and include a property with the name wrappedValue property, defined as a computed property with a getter and a
wrappedValue to process and store the value. The structure must also include setter. The getter returns the current value of the storedValue property, and
an initializer for the wrappedValue property. The following is a Playground the setter checks whether the new value is within the minimum and
example that illustrates how to define a property wrapper that limits the maximum allowed before storing it. If the new value exceeds a limit, the
value of a property to a minimum of 0 and a maximum of 255. values of the min or max properties are assigned to the storedValue property
instead.
Listing 6-1: Defining a property wrapper The code in Listing 6-1 defines the property wrapper, but it doesn't define
 any property of this kind. Implementing a property wrapper is easy, we
import Foundation must declare the properties as we always do but preceded with the name
@propertyWrapper
of the property wrapper prefixed with the @ character.
struct ClampedValue {
var storedValue: Int = 0 Listing 6-2: Using a property wrapper
var min: Int = 0
var max: Int = 255 
struct Price {
var wrappedValue: Int { @ClampedValue var firstPrice: Int
get { @ClampedValue var secondPrice: Int
return storedValue
} func printMessage() {
set { print("First Price: \(firstPrice)") // "First Price: 0"
if newValue < min { print("Second Price: \(secondPrice)") // "Second Price: 255"
storedValue = min }
} else if newValue > max { }
storedValue = max var purchase = Price(firstPrice: -42, secondPrice: 350)
} else { purchase.printMessage()
storedValue = newValue
 @State
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 Property wrappers allow us to define properties that can perform tasks
values -42 and 350. Both values exceed the limits established by the with the values assigned to them. SwiftUI implements property wrappers
property wrapper, so the value stored for each property is the limit they extensively to store values and report the changes to the views. The one
exceeded (0 for firstPrice and 255 for secondPrice). designed to store the states of a single view is called @State. This property
wrapper stores a value in a structure of type State and notifies the system
Do It Yourself: Create a new Playground file and replace the code in when that value changes, so the views are automatically updated to reflect
the template with the codes in Listings 6-1 and 6-2. Press play. You the change on the screen.
The @State property wrapper was designed to store the states of a single
should see the messages " First Price: 0" and " Second Price: 255"
view, so we should declare the properties of this type as part of the view
printed on the console.
structure and as private, so the access is restricted to the structure in which
they were declared.
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, Listing 6-4: Defining multiple states
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 struct ContentView: View {
label as a Text view with the text "Change Title", so the user knows that it @State private var title: String = "Default Title"
@State private var titleActive: Bool = false
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". var body: some View {
VStack {
The title property created with the @State property wrapper is used in two Text(title)
places, first in the Text view to show the current value to the user, and then .padding(10)
.foregroundColor(titleActive ? Color.red : Color.gray)
in the action for the Button view to modify its value. In consequence, every
Button(action: {
time the button is pressed, the value of the title property changes, the title = "My New Title"
@State property wrapper notifies the system, and the content of the body titleActive = true
}, label: { Text("Change Title") })
property is automatically refreshed to display the new value on the screen. Spacer()
}.padding()
}
Figure 6-2: Initial state (left) and state after the button is pressed (right) }

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
Do It Yourself: Create a new Multiplatform project. Update the operator to set the state of the view is the recommended practice because
ContentView view with the code in Listing 6-3. Make sure that Live it allows the system to determine all the possible states the view can
Preview is activated on the canvas (Figure 5-18, number 1). Press the respond to and produce a smooth transition from one state to another. if
Change Title button to assign the string to the Text view. You should the value of titleActive is true, we assign the color red to the foregroundColor()
see something similar to Figure 6-2, right. modifier, otherwise, we assign the color gray.
In the button's action, besides assigning a new value to the title property,
All this process works automatically. We don't have to assign the new value we now assign the value true to the titleActive property to change this state.
to the Text view or tell the view that a new value is available, it's all done by The titleActive property informs the system that there is a new value
the @State property wrapper. And we can include all the @State properties available, the system refreshes the views, the ternary operator is evaluated
we need to store every state of the interface. For instance, the following again, and in consequence the color red is assigned to the text.
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 IMPORTANT: There are two states in the example of Listing 6-4, and
color to the text. both change at the same time, but the system takes into
consideration these situations and makes sure that the interface is titleActive = true
titleInput = ""
updated only when necessary. }, label: { Text("Change Title") })
Spacer()
}.padding()
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 
Listing 6-5: Defining bidirectional binding Do It Yourself: Update the ContentView view with the code in Listing 6-
 5. Click on the text field and insert a text. Press the Change Title
struct ContentView: View { button. You should see something similar to Figure 6-1, right.
@State private var title: String = "Default Title"
@State private var titleActive: Bool = false
@State private var titleInput: String = ""
Binding Structures 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
As we have already learned, property wrappers are defined as structures
with @State properties, but in theory we can also access the properties
and therefore they contain their own properties. SwiftUI allows access to
directly, as in the following example.
the underlying structure of a property wrapper by prefixing the property's
name with an underscore (e.g., _title). Once we get the structure, we can Listing 6-7: Accessing the properties of a State structure

access its properties. The structure that defines the @State property struct ContentView: View {
@State private var title: String = "Default Title"
wrapper is called State. This is a generic structure and therefore it can @State private var titleInput: String = ""
process values of any type. The following are the properties defined by this var body: some View {
VStack {
structure to store the state's values. Text(_title.wrappedValue)
.padding(10)
TextField("Insert Title", text: _titleInput.projectedValue)
.textFieldStyle(.roundedBorder)
Button(action: {
wrappedValue—This property returns the value managed by the _title.wrappedValue = _titleInput.wrappedValue
_titleInput.wrappedValue = ""
@State property. }, label: { Text("Change Title") })
Spacer()
projectedValue—This property returns a structure of type Binding }.padding()
}
}
that creates the bidirectional binding with the view. 
This is the same example as before, but instead of using the SwiftUI
The wrappedValue property stores the value we assign to the @State property,
shortcuts, we read the wrappedValue and projectedValue properties of the State
like the "Default Title" string assigned to the title property in the last
structure directly. Of course, this is not necessary, but may be required
example. The projectedValue property stores a structure of type Binding that
sometimes to overcome SwiftUI's shortcomings. For instance, SwiftUI
creates the bidirectional binding we need to store a value back to the
doesn't allow us to access and work with @State properties outside the
property. If we read the @State property directly (e.g., title), the value
closure assigned to the body property, but we can replace one State structure
by another. For this purpose, the State structure includes the following possible, we should use the property wrappers provided by SwiftUI
and initialize a @State property with the onAppear() modifier introduced
initializers. in Chapter 5 (see Listing 5-54) or by storing the states in an
observable object, as we will see later.
Do It Yourself: Update the ContentView view with the code in Listing 6- projectedValue—This property returns a structure of type Binding
7. Add the initializer in Listing 6-8 to the ContentView structure (below that creates the bidirectional binding with the view.
the @State properties). You should see the input field initialized with
"Hello World".
As we did with the State structure, we can access and work with the values
IMPORTANT: Accessing the content of binding properties this way is
stored in the Binding structure. For instance, the following example
only recommended when there are no other options. When
Listing 6-10: Creating a Binding structure Do It Yourself: Add the structure in Listing 6-11 to the
 ContentView.swift file. You should see an additional button at the
struct HeaderView_Previews: PreviewProvider { top of the canvas to display the preview of the HeaderView view.
static var previews: some View {
let constantValue = Binding<String>(
get: { return "My Preview Title"},
set: { value in
print(value)
})
return HeaderView(title: constantValue)
}
}

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
Besides custom states, we can also respond to states set by the system in
In Listing 6-12, we define a property called mode to create a binding
the environment. We have worked with the environment before to
between the view and the environment's colorScheme property, and then
configure previews (see Environment in Chapter 5), but some of its assign different values to the modifiers of a Text view depending on the
properties, like colorScheme and dynamicTypeSize, represent states that can current appearance (dark or light). When the user changes the appearance
change while the app is running, so we can read their values and update from Settings, the value of the colorScheme property in the environment
the views accordingly. For this purpose, SwiftUI includes the @Environment changes, this changes the value of the mode property in our view, and the
property wrapper. This property wrapper works in a similar way to @State view is updated according to the new state. Notice that we check the value
but it is designed to report to the system changes in the environment of the mode property in the modifiers with a ternary operator, as we did
properties. The @Environment property wrapper is created with the before, so SwiftUI can effectively process the changes. If the appearance is
Environment structure. The following is the structure's initializer. light, we display a blue trash can with a circle around it, otherwise, we
display the can's fill variant in yellow.
Environment(KeyPath)—This initializer creates an Environment
Figure 6-4: States in light and dark appearances
structure that provides access to an environment property. The
argument is the key path of the property we want to access.
The following is an example of how to define this class. We call it Figure 6-7: Observable object model
ApplicationData, but it can take any name we want. The ApplicationData class
conforms to the ObservableObject protocol and includes two @Published
properties to store the data.
Because the source of data (also called the source of truth) has to be The instance of our model is stored in a @StateObject property. This ensures
unique, we cannot create one object for each view, we have to create only that the instance will never be recreated and it is the same for every view.
one object and then pass a reference to that object to all the views. Figure Once the instance is created, we pass it to the initial view (ContentView). This
6-7, below, illustrates this scheme. We store an instance of our model in a view must include an @ObservedObject property to create a bidirectional
@StateObject property and then pass a reference to the initial view, which in binding with the @StateObject property and access the model, as shown
turn passes that reference to the next view and so on. next.
Listing 6-15: Working with the observable object from the views ApplicationDataobject to this structure, as we did in Listing 6-15
 (ContentView(appData: ApplicationData())). Now, the preview on the canvas can
struct ContentView: View { also run the application.
@ObservedObject var appData: ApplicationData
class ApplicationData: ObservableObject { Everything is the same as before, but we have defined an additional
@Published var title: String = "Default Title" observable object for the ContentView view called ContentViewData, and now
}
 the characters inserted by the user are stored in the @Published property of
this object (titleInput). When the user presses the Save button, we assign the
Our model now only stores the title of the book. To manage the states of value of this property to the title property to store the data in the model.
the view and provide the binding properties for the TextField view, we can The advantage of using an observable object and @Published properties to
define an additional observable object for the ContentView view, as in the manage the states of a view instead of @State properties is that it is easy to
following example. initialize the properties of the object dynamically. For instance, we can
assign the current title stored in the model to the titleInput property as soon
Listing 6-17: Defining an observable object for a view as the ContentView structure is initialized, so the user can see the previous
 value on the screen.
import SwiftUI
Listing 6-18: Initializing the view's observable object
class ContentViewData: ObservableObject {

@Published var titleInput: String = ""
} init(appData: ApplicationData) {
struct ContentView: View { self.appData = appData
@ObservedObject var contentData = ContentViewData() contentData.titleInput = self.appData.title
@ObservedObject var appData: ApplicationData }

var body: some View {
VStack(spacing: 8) { The initializer passes the reference of the model to the appData property
Text(appData.title)
.padding(10) and then assigns the value of the model's title property to the titleInput
TextField("Insert Title", text: $contentData.titleInput) property of the contentData object, so the TextField view shows the current
.textFieldStyle(.roundedBorder) value.
Button(action: {
appData.title = contentData.titleInput
}, label: { Text("Save") }) Figure 6-9: Text field initialized with the value in the model
Spacer()
}.padding()
}
}
struct ContentView_Previews: PreviewProvider {
@EnvironmentObject

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
Do It Yourself: Update the ApplicationData.swift file with the code in data and therefore all of them must contain a reference to the model. But
Listing 6-16 and the ContentView.swift file with the code in Listing 6- passing this reference from one view to another until we reach the one
17. You should see something similar to Figure 6-8. Add the initializer 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
of Listing 6-18 to the ContentView structure (below the @ObservedObject
to the environment and then read it from the environment whenever we
properties) and run the application again. You should see something
need it.
like Figure 6-9.
Figure 6-10: Accessing the model through the environment
Another way to initialize properties of an observable object or @State
properties is with the onAppear() modifier. As we have seen before, this
modifier executes a closure when the view appears on the screen. For
instance, we can assign it to the VStack view of our ContentView view to
initialize the titleInput property as soon as the view is shown on the screen.

Listing 6-19: Initializing the view's observable object when the views
appear As we already mentioned, the environment is a general-purpose container
 that stores information about the app and the views, but it can also store
.onAppear { custom data, including references to observable objects. In the example of
contentData.titleInput = appData.title
}
Figure 6-10, an instance of the observable object is added to the
 environment, and then accessed only by the views that require it (View 2
and View 4). To add the observable object to the environment, the View
Do It Yourself: Remove the initializer introduced in Listing 6-18 from protocol includes the following modifier.
the ContentView structure. Add the onAppear() modifier of Listing 6-19
to the VStack view (below the padding() modifier). Run the application environmentObject(Object)—This modifier assigns an object to
again. The result should be the same as before. the environment. The argument is a reference to the object we want
to share with the views.
And to access the object from the views, SwiftUI include the Listing 6-21: Getting a reference to the observable object from the
@EnvironmentObject property wrapper, which is defined by the following environment
structure. 
import SwiftUI
EnvironmentObject()—This initializer creates an EnvironmentObject
class ContentViewData: ObservableObject {
structure that provides access to an environment object. @Published var titleInput: String = ""
}
struct ContentView: View {
The environmentObject() modifier assigns an object to the environment of a @ObservedObject var contentData = ContentViewData()
view's hierarchy, so we must apply it to the initial view for all the views on @EnvironmentObject var appData: ApplicationData
the interface to have access to the model. The following code shows the
var body: some View {
modifications we must introduce to the App structure to create the VStack(spacing: 8) {
ApplicationData object and add it to the environment of the ContentView view. Text(appData.title)
.padding(10)
TextField("Insert Title", text: $contentData.titleInput)
Listing 6-20: Assigning the observable object to the view's environment .textFieldStyle(.roundedBorder)
Button(action: {

appData.title = contentData.titleInput
import SwiftUI }, label: { Text("Save") })
Spacer()
}.padding()
@main
.onAppear(perform: {
struct TestApp: App {
contentData.titleInput = appData.title
@StateObject private var appData = ApplicationData()
})
}
var body: some Scene { }
WindowGroup { struct ContentView_Previews: PreviewProvider {
ContentView() static var previews: some View {
.environmentObject(appData) ContentView()
} .environmentObject(ApplicationData())
} }
} }
 
Instead of passing the reference of the model to the ContentView view, as we There are two changes in this code from the previous example. Instead of
did before (see Listing 6-14), we pass it to the view's environment. Now, to storing the reference of the model in an @ObservedObject property, we use
access the model from the views, we just have to get the reference from an @EnvironmentObject property. The reference works the same way, but the
the environment with the @EnvironmentObject property wrapper, as shown @EnvironmentObject property automatically gets the reference from the
next. 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 6.3 View Model
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
Do It Yourself: Update the App structure with the code in Listing 6-
transform the values before presenting them to the user. But views in
20, and the ContentView.swift file with the code in Listing 6-21. The
SwiftUI should only be responsible of defining the interface. In addition,
application works as before, but now the values are taken from the multiple views may need to perform the same transformations on the data,
observable object through the environment and therefore they are but doing so from different views can be error prone. For instance, we may
available to all the views that belong to the ContentView's hierarchy. have an application that stores and displays the title and author of a book.
We will learn how to add more views to this hierarchy in Chapter 8. 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
IMPORTANT: The environment stores key/value pairs. These types of shown to the user. But if this process is performed from multiple views,
values are like dictionary values, they have a key and a value mistakes can be made. We may forget to join the strings and only show
associated to that key. When we apply the environmentObject() modifier one value, or do it in the wrong order. To avoid mistakes, the string should
with an instance of our observable object, the environment stores an not be created by the views but provided by the model, ready to use.
item with the ApplicationData key (the object's data type) and the There are different patterns we can adopt to improve the organization of
object as the value. When an @EnvironmentObject property is defined, our application. The one recommended by Apple is called Model View
the property wrapper looks for an item which key is the property's View-Model (MVVM). In this pattern, there is a model with the basic
data type and assigns its value to the property. information, a view-model that prepares that information for the views,
and the views that present the information to the user.
Listing 6-22: Defining an MVVM pattern Listing 6-23: Accessing the model in an MVVM pattern
 
import SwiftUI import SwiftUI
Controls are visual tools the user interacts with to change the state of the As we have already seen, the Button view creates a simple control that
interface, select options, or insert, modify or delete information. We have performs an action when pressed. The following are some of the
implemented some of them already, like the Button view and the TextField structure's initializers.
view used in previous examples. To define a useful interface, we need to
learn more about these views and the rest of the control views provided by Button(String, action: Closure)—This initializer creates a Button
SwiftUI. view. The first argument is a string that defines the button's label, and
the action argument is a closure with the code to be executed when
the button is pressed.
Button(action: Closure, label: Closure)—This initializer creates a
Buttonview. The action argument is a closure with the code to be
executed when the button is pressed, and the label argument is a
closure that returns the views used to create the label.
Button(String, role: ButtonRole?, action: Closure)—This
initializer creates a Button view. The first argument is a string that
defines the button's label. The role argument is a structure with type
properties to describe the purpose of the button. There are two
properties available: cancel and destructive. And the action argument is a
closure with the code to be executed when the button is pressed.
Figure 6-13: Button view If the only action performed by the button is to call a method, we can
simplify the definition of the view by declaring the action argument and
specifying the name of the method as the action to perform, as shown
below.

Listing 6-26: Referencing a method
Do It Yourself: Create a Multiplatform project. Update the ContentView 
view with the code in Listing 6-24. Press the Change Color button. Button("Change Color", action: changeColor)
Of course, if we want to separate the views from the actions performed by Declaring the name of a method with parentheses executes the method
the controls, we can move the statements to a function. For instance, we right away, but declaring only the name provides a reference to the
can add a function to the ContentView structure to toggle the value of the method that the system can use later to execute it.
colorActive property and then call this function from the button's action. The
application works the same way, but the code is better organized.
Do It Yourself: Update the ContentView view with the code in Listing 6- content of the body property is drawn again, and the Text view appears or
25. Refresh the live preview if necessary. The application should disappears, depending on the current value of the property.
work the same as before. Replace the Button view with the Button view
in Listing 6-26. Refresh the live preview again and press the button to Figure 6-14: Dynamic interface
confirm the action is performed.
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.
Do It Yourself: Download the expand and contract images from our
Listing 6-29: Defining the label of a button with an Image view website and add them to the Asset Catalog. Update the ContentView
 view with the code in Listing 6-29 and press the Expand button. You
should see the interfaces in Figure 6-16. Remove the renderingMode() print("Send Information")
}.buttonStyle(.borderedProminent)
modifier. You should see the images in their original colors.
}
Spacer()
We can also assign standard styles for the buttons with the following }.padding()
}
modifiers. }

Do It Yourself: Update the ContentView view with the code in Listing 6- Figure 6-19: Button of custom size
30. You should see the button as illustrated in Figure 6-17. Replace
the Cancel button with the Button view in Listing 6-31. Refresh the live
preview. You should see the destructive button as illustrated in
Figure 6-18. 
These styles were designed to look good with SF Symbols. The advantage If we want to define a style that deviates from those provided by the
of using SF Symbols instead of our own images is that they are scaled to system, we must create our own ButtonStyle structure. The protocol's only
the size of the font assigned to the button. This, along with the possibility requirement is for the structure to implement the following method.
of scaling the button itself with the controlSize() modifier, allows us to create
buttons of different sizes. makeBody(configuration: Configuration)—This method defines
and returns a view that replaces the body of the button. The
Listing 6-32: Scaling buttons
configuration argument is a value of type Configuration that contains

information about the button.
struct ContentView: View {
var body: some View {
VStack(spacing: 10) { This method receives a value of type Configuration, a typealias of
Button(action: {
print("Delete item") ButtonStyleConfiguration, which contains properties that return information
}, label: { about the button. The following are the properties available.
HStack {
Image(systemName: "mail")
.imageScale(.large) isPressed—This property returns a Boolean value that indicates
Text("Send")
} whether the button was pressed or not.
})
.buttonStyle(.borderedProminent) label—This property returns the view or views that define the
.font(.largeTitle)
.controlSize(.large)
button's current label.
Spacer()
}.padding()
}
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 return the label. First, we read the value of the isPressed property to know if
makeBody() method, and return from this method the view we want to the button was pressed or not, and then apply the new styles to the label
assign to the body of the button. property. This property returns a copy of the views that create the button's
The views that make up the body of the button are provided by the label current label, and therefore by modifying its value we are effectively
property of the Configuration structure, so we can read and modify the value modifying the label. In this case, we apply a padding, a border, and then
of this property to apply the new styles, as shown next. assign a scale depending on the value of the isPressed property. If the value
is true, which means the button is being pressed, we assign a scale of 1.2 to
Listing 6-33: Defining custom styles for the button expand it, but if the value is false, we bring the scale back to 1.
 In the view, we create an instance of this structure and assign it to the
import SwiftUI Button view with the buttonStyle() modifier. The result is shown below.
creates an input field the user can interact with to insert a value (a line of emailAddress, decimalPad, twitter, webSearch, asciiCapableNumberPad, and
text). The following is one of the initializers included by the structure. alphabet.
TextField(String, text: Binding, axis: Axis)—This initializer creates We have already seen how to include a simple TextField view to get input
an input field. The first argument defines the field's placeholder, the from the user, but we haven't applied any modifiers yet. The following
text argument is the Binding property used to store the value inserted example shows how to style the view and how to capitalize words.
by the user, and the axis argument defines the axis in which the
Listing 6-34: Configuring a text field
content will scroll when it exceeds the bounds of the view. It is an

enumeration with the values horizontal and vertical.
struct ContentView: View {
@State private var title: String = "Default Title"
The framework defines a few modifiers for TextField views. The following are @State private var titleInput: String = ""
Do It Yourself: Update the ContentView structure with the code in Listing 6-36: Responding to changes in focus
Listing 6-35 and run the app on the iPhone simulator. Click on the 
input field, insert a text, and press the Continue key on the import SwiftUI
}

The Button view in Listing 6-37 replaces the Button view of Listing 6-36. Now,
every time the Save button is pressed, the values are processed and the
The initial value of the @FocusState property is nil, which means no view is
keyboard is closed.
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
Do It Yourself: Update the ContentView.swift file with the code in
comparing this value with the values in the enumeration, we know which
TextField view is focused and can change the background color accordingly.
Listing 6-36 and run the application on the iPhone simulator. Click on
Notice that the roundedBorder style adds a border and a white background to an input field. You should see the background color changing to gray,
the text field, so only the background of the padding is visible in this as illustrated in Figure 6-22. Replace the Button view with the view in
example. Listing 6-37. Run the application again. Insert values in both fields
and press the Save button. The values should be assigned to the title
Figure 6-22: Focus and the virtual keyboard should be closed.
In mobile devices, the virtual keyboard opens when a view that can Listing 6-38: Checking the values before storing
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 Button("Save") {
let tempName = nameInput.trimmingCharacters(in: .whitespaces)
means that to close it, we must remove focus from the view. In SwiftUI this let tempSurname = surnameInput.trimmingCharacters(in: .whitespaces)
is achieved by assigning the value nil to the @FocusState property, as shown
next. if !tempName.isEmpty && !tempSurname.isEmpty {
title = tempName + " " + tempSurname
focusName = nil
Listing 6-37: Closing the keyboard }
 }
Button("Save") { 
title = nameInput + " " + surnameInput
focusName = nil In this example, we first trim the nameInput and surnameInput properties to
}
 remove spaces at the beginning and the end (see Strings in Chapter 4) and
then check that the resulting values are not empty before assigning them accept numbers or a specific amount of characters. For this, we need to
to the title property. The Save button is still enabled, but the values are not check whether the value inserted by the user is valid or not every time the
stored until the user inserts a text in both fields. state of the view changes. The framework includes the following modifier
for this purpose.
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 onChange(of: Value, perform: Closure)—This modifier executes a
you have inserted a name and a surname. closure when a state changes. The of argument is the value to check,
and the perform argument is the closure we want to execute when
Another alternative is to disable the button with the disabled() modifier if the system reports a change in the value.
the values inserted by the user are not what the application is expecting.
This modifier can only check one value, so we should apply it to every view
Listing 6-39: Disabling the button 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.
Button("Save") { If the user goes over the limit, we remove the extra characters and assign
let tempName = nameInput.trimmingCharacters(in: .whitespaces)
let tempSurname = surnameInput.trimmingCharacters(in: .whitespaces)
the result back to the property, as shown next.
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.
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.
Do It Yourself: Create a Multiplatform project. Update the ContentView
view with the code in Listing 6-43. Insert characters in the input field.
SecureField(String, text: Binding)—This initializer creates a secure
You should see the characters being replaced by black dots, as
input field. The first argument defines the text field's placeholder, and
the text argument is the Binding property that stores the value inserted illustrated in Figure 6-24.
by the user.
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.
Toggle View 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 Toggle view creates a control to switch between two states. By default,
Figure 6-26: Switch on and off
it is displayed as a user-friendly toggle switch on mobile devices and as a
checkbox on Macs. The view includes the following initializers.

Toggle(String, isOn: Binding)—This initializer creates a Toggle view.
The first argument defines the control's label, and the isOn argument Do It Yourself: Create a new Multiplatform project. Update the
is the Binding property that stores the current state. The view also ContentView view with the code in Listing 6-45. Click on the switch to
includes an initializer to define the label with the views returned by a turn it on and off. Use this project to test the following examples.
closure (Toggle(isOn: Binding, label: Closure)).
The closure assigned to the label argument can include a second view to
The view requires a Binding property to store the current value. In the define a subtitle, as in the following example.
following example, we provide a @State property and use the value of this
property to select the proper label. Listing 6-46: Including a subtitle

Listing 6-45: Implementing a Toggle struct ContentView: View {
 @State private var currentState: Bool = true
struct ContentView: View {
@State private var currentState: Bool = true var body: some View {
VStack {
var body: some View { Toggle(isOn: $currentState, label: {
VStack { Text(currentState ? "On" : "Off")
Toggle(isOn: $currentState, label: { Text("Enable or Disable")
})
Text(currentState ? "On" : "Off")
Spacer()
})
}.padding()
Spacer()
}
}.padding() }
}

}

Figure 6-27: Switch with a title and subtitle
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

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 Like Button views, Toggle views also implement a modifier to define the style
on the position and size of the views, we can apply the fixedSize() modifier of the control.
introduced before to reduce the view's size, or hide the label with the
following modifier. toggleStyle(ToggleStyle)—This modifier defines the style of the
toggle. The argument is a structure that conforms to the ToggleStyle
labelsHidden()—This modifier hides the labels assigned to controls. protocol. To create standard structures, the framework includes the
properties automatic, button, checkbox, and switch.
This modifier works with several controls, but it is particularly useful with
switches. The following example shows how to implement it to define a The value by default is automatic, which means the style of the control is
custom label for the control. 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).
Listing 6-47: Defining a custom label for the Toggle view These values are used to specify standard styles, but the framework also
 includes the value button to create a completely different type of control.
struct ContentView: View { When we assign this style to the view, the system shows a toggle button to
@State private var currentState: Bool = true
represent the on and off states. When the button is in the on state, it is
var body: some View { highlighted, otherwise it is shown as a standard button.
HStack {
Toggle("", isOn: $currentState)
.labelsHidden() Listing 6-48: Implementing a toggle button
Text(currentState ? "On" : "Off") 
.padding()
.background(Color(currentState ? .yellow : .gray)) struct ContentView: View {
}.padding() @State private var currentState: Bool = true
}
} var body: some View {
 HStack {
Toggle(isOn: $currentState, label: {
Label("Send", systemImage: "mail")
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 .toggleStyle(.button)
}.padding()
include a Text view on the side to display the current value. }
}

Figure 6-28: Custom size and label for the switch
import SwiftUI
Figure 6-29: Toggle button in the on and off state struct MyStyle: ToggleStyle {
func makeBody(configuration: MyStyle.Configuration) -> some View {
HStack(alignment: .center) {
configuration.label
 Spacer()
Image(systemName: "checkmark.rectangle.fill")
.font(.largeTitle)
The styles offered by the framework are limited, but we can create our .foregroundColor(configuration.isOn ? Color.green : Color.gray)
own. All we have to do is to define a structure that conforms to the .onTapGesture {
ToggleStyle protocol. The protocol's requirement is for the structure to configuration.$isOn.wrappedValue.toggle()
}
implement the following method. }
}
}
makeBody(configuration: Configuration)—This method defines struct ContentView: View {
and returns a view that replaces the body of the toggle. The @State private var currentState: Bool = false
configuration argument is a value of type Configuration that contains var body: some View {
information about the control. VStack {
HStack {
Toggle("Enabled", isOn: $currentState)
This method receives a value of type Configuration, a typealias of .toggleStyle(MyStyle())
}
ToggleStyleConfiguration, which contains the following properties to return Spacer()
information about the control. }.padding()
}
}
isOn—This property returns a Boolean value that indicates if the 
toggle is on or off.
There are a few things we must consider before customizing a Toggle view.
label—This property returns the view that defines the toggle's label.
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
The isOn property is a Binding property that creates a bidirectional binding
label, we must include the value of this property in the new content.
with the view and therefore we can read and modify its value to activate or
Second, a Toggle view is designed with an HStack view and a Spacer view
deactivate the control. In the following example, we create a Toggle view
between the label and the control. If we want to stick to the standard
that looks like a checkbox. When the control is tapped, the graphic changes
design, we must preserve this arrangement. And third, we are responsible
color indicating the current state (gray deactivated and green activated).
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
Listing 6-49: Defining a custom Toggle view
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 Slider View
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
A Slider view creates a control that allows the user to select a value from a
first read the value of the label property to include the current label, then
range of values. It is displayed as a horizontal bar with a knob that indicates
declare the Spacer view, and finally define an Image view that presents an SF
the selected value. The structure includes the following initializer.
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
Slider(value: Binding, in: Range, step: Float,
property, and finally, use the onTapGesture() modifier to detect when the user onEditingChanged: Closure)—This initializer creates a Slider view.
taps on the Image view. We will learn more about gesture modifiers in The value argument is the Binding property we want to use to store the
Chapter 12. For now, all we need to now is that this modifier executes a current value, the in argument is a range that specifies the minimum
closure every time the view is tapped by the user. In this closure, we access and maximum values the user can choose from, the step argument
the Binding value of the isOn property and toggle its value by applying the indicates the number by which the current value will be incremented
toggle() modifier to the Boolean value stored in its wrappedValue property. (In or decremented, and the onEditingChanged argument is a closure
this case, the setter of the Binding value is private, so we access it from the that is executed when the user starts or finishes moving the slider.
wrappedValue property, as explained before in this chapter.) This modifies the
current value of the property, which changes the state of the control, To create a slider, we must provide at least a @State property to store the
turning it on and off. value and a range to determine the minimum and maximum values
allowed.
Figure 6-30: Custom style for the Toggle view
Listing 6-50: Creating a slider

 struct ContentView: View {
@State private var currentValue: Float = 5
The code in Listing 6-50 creates a slider from the value 0 to 10 and displays }

the current value with a Text view. The Slider view takes values of type Float
or Double, and therefore it allows us to select a floating-point value, but we
The view in Listing 6-51 includes a new @State property called textActive. The
can specify that we want the user to be able to select only integers by
closure assigned to the onEditingChanged argument assigns the value true
declaring the step argument with the value 1.0, as we did in this example.
to this property when the user begins moving the slider and the value false
(Notice that we had to format the value for the Text view with the formatted()
when the user let the knob go. The background() modifier of the Text view
method introduced in Chapter 4 to display it as an integer.) Because the
reads the value of this property to assign a different background color to
currentValue property was initialized with the number 5, the initial position
the view depending on the current state. Because of this, the text that
of the knob is right in the middle.
displays the slider's current value has a yellow background while the user is
moving the slider, or no color otherwise.
Figure 6-31: Slider for integer values
Figure 6-32: Slider states
The ProgressView structure includes the following modifier to define the style Stepper View
of the progress bar.

progressViewStyle(ProgressViewStyle)—This modifier specifies
The Stepper view creates a control with two buttons to increment or
the style of a ProgressView view. The argument is a structure that
decrement a value. The structure provides multiple initializers with several
conforms to the ProgressViewStyle protocol. The framework defines the combinations of arguments for configuration. The following are the most
properties automatic, circular, and linear to create standard views. frequently used.
The style by default is automatic, which means that the view is going to be Stepper(String, value: Binding, in: Range, step: Float,
shown as a linear progress bar, but we can specify the circular value to onEditingChanged: Closure)—This initializer creates a Stepper view.
create an activity indicator. This is a spinning wheel that indicates that a
The first argument defines the label, the value argument is the Binding
task is in progress, but unlike progress bars, this type of indicator has no
property we want to use to store the current value, the in argument is
implicit limitations, so we don't need to specify any values, as shown next.
a range that determines the minimum and maximum values allowed,
Listing 6-54: Showing an activity indicator the step argument is a Float or a Double (depending on the Binding

property) that determines the amount by which the value is going to
struct ContentView: View { be increased or decreased, and the onEditingChanged argument is a
@State private var currentValue: Float = 5 closure that is executed when the user begins and ends editing the
value.
var body: some View {
VStack { Stepper(String, onIncrement: Closure?, onDecrement:
ProgressView()
.progressViewStyle(.circular) Closure?, onEditingChanged: Closure)—This initializer creates a
Spacer()
}.padding()
Stepperview. The first argument defines the label, the onIncrement
} argument is a closure that is executed when the user taps on the +
}
 button, the onDecrement argument is a closure that is executed when
the user taps on the - button, and the onEditingChanged argument is
Figure 6-35: Activity indicator a closure that is executed when the user begins and ends editing the
value.
In this example, we define two @State properties: currentValue to store the GroupBox View
current value of the stepper, and a Boolean property called goingUp to

indicate whether the last value was incremented or decremented. The
views are included in an HStack to show them side by side. The first one is
SwiftUI includes a view called GroupBox to create a box around the views.
the same Text view used before to display the stepper's current value. After
The view is defined with a background color and round corners to visually
this view, we include an Image view that checks the goingUp property to show
group views and controls. The following is one of the view's initializers.
an SF Symbol of an arrow pointing up or down, depending on the
property's value. The same property is used to determine the color of the
arrows. Finally, we define the Stepper view with the onIncrement and
GroupBox(String, content: Closure)—This initializer creates a
onDecrement arguments. In the closures assigned to these arguments, we GroupBox view. The first argument defines the label to show at the top
increment or decrement the value by 5 and change the value of the goingUp of the box, and the content argument is the closure that defines the
property to indicate whether the last value was incremented or views contained by the group.
decremented. As a result, the user can see a green arrow pointing up when
the + button is pressed and a red arrow pointing down when the - button is The view is styled by default with a background color, so all we need to do
pressed. is to implement it and provide the closure with all the views we want to
include inside the box.
Figure 6-37: Custom stepper
Listing 6-58: Defining a group of views

 struct ContentView: View {
@State private var setting1: Bool = true
@State private var setting2: Bool = true
@State private var setting3: Bool = true
Size Classes
As explained before, we access these environment values with the

@Environment property wrapper (see Listing 6-12). Once the @Environment
Because of the rectangular shape of the screen, the interface is defined by properties are created, adapting the interface is a matter of organizing the
two size classes, one for the horizontal and another for the vertical space. views according to their values (compact or regular). For instance, in the
Every device is assigned different size classes, depending on the size of following example we define the interface's content in two custom views,
their screens and orientations, as illustrated below. HeaderView and BodyView, and then display them in a vertical or horizontal
stack depending on the current horizontal size class.
Figure 6-39: Size classes assigned to mobile devices
Listing 6-59: Detecting changes in the horizontal size class

struct ContentView: View {
@Environment(\.horizontalSizeClass) var horizontalClass
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
Do It Yourself: Create a Multiplatform project. Update the
compact, which means that the interface is being presented in a small space,
ContentView.swift file with the code in Listing 6-59. Select a large
we create a VStack to show the views on top of each other, otherwise, we
iPhone that presents the interface in two size classes, such as the
create an HStack to show them side by side.
iPhone 13 Pro Max. Rotate the preview. You should see the
Organizing the views on the screen is not enough to adapt the
interfaces illustrated in Figure 6-40.
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
IMPORTANT: Notice that isCompact is a normal property. We didn't
indicate whether the interface is been shown in a compact or a regular
have to use a Binding property in this case because every time the
space, and then use this value inside the view to select the appropriate value of the horizontal property changes, the views are redrawn and
height. the isCompact property is updated with the current value.
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
GeometryReader View 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
Apple recommends to always adapt the interface according to the size
the space available and sends a GeometryProxy value with its position and
classes, but there are times when this information is not enough. For
instance, apps running fullscreen on iPads are always Regular and Regular size to the closure. Using these values, we can adapt the views or perform
(see Figure 6-39), but the difference between the width and height in any change on the content we want, as shown below.
portrait and landscape orientation is still significant. In cases like this, we
must adapt the interface according to the size of the views, the window, or Listing 6-60: Using a GeometryReader to detect orientation

the screen. SwiftUI includes a view called GeometryReader that can help us on
struct ContentView: View {
this task. The GeometryReader view takes the size of its container and sends var body: some View {
this information to its children, so they can adapt to those values. GeometryReader { geometry in
let isPortrait = geometry.size.height > geometry.size.width
GeometryReader(content: Closure)—This initializer creates a let message = isPortrait ? "Portrait" : "Landscape"
Listing 6-61: Adapting an Image view to the size of its container Do It Yourself: Update the ContentView view with the code in Listing 6-
 61. Download the spot1 image from our website and add it to the
struct ContentView: View {
var body: some View {
Asset Catalog. Run the application on the iPhone simulator. Rotate
GeometryReader { geometry in
HStack {
the screen. You should see the Image view adapting to the space center of the screen, which means that the position of the GeometryReader
available on the screen. view on the screen will be different in portrait and landscape orientation.
Figure 6-43, below, shows what we see when we run this application on a
Besides the size, we can also get the position of the GeometryReader view. small iPhone. In portrait, the GeometryReader view is 60 points from the left
This information is returned by the frame() method of the GeometryProxy and 169 points from the top, but in landscape, the view is 184 points from
structure and the values depend on the selected coordinate space. A the left and 35 points from the top.
SwiftUI interface defines two types of coordinates spaces, one called global
that represents the coordinates of the screen, and another called local that Figure 6-43: Horizontal and vertical positions
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. 
Listing 6-62: Reading the position of the GeometryReader view Do It Yourself: Update the ContentView view with the code in Listing 6-

62. Run the application on the iPhone simulator. Rotate the screen.
struct ContentView: View {
var body: some View { You should see the values of the x and y coordinates change because
GeometryReader { geometry in of the changes in the GeometryReader view's position relative to the
let globalX = Int(geometry.frame(in: .global).origin.x)
let globalY = Int(geometry.frame(in: .global).origin.y) screen.
Text("Position: \(globalX) / \(globalY)")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}.frame(width: 200, height: 250) The GeometryReader view is a flexible view and therefore it takes all the space
.background(.gray)
}
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
This view includes a Text view embedded in a GeometryReader view. We made
background to an image and embed the background view in a
the Text view flexible to fill the container, and gave the GeometryReader view a
GeometryReader view to determine the size of the image.
fixed size of 200 by 250. Because of this, the views are positioned at the
Listing 6-63: Reading the position and size of a view 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
struct ContentView: View { size of the Image view at the bottom, as shown in Figure 6-44.
@State private var size: CGSize = .zero
var body: some View { IMPORTANT: Notice that the onAppear() modifier is only called the
VStack { first time the view appears on the screen. This means that the values
Image("spot1")
.resizable() are not updated when the device is rotated. To have access to these
.scaledToFit() values at any time, we need to implement Preferences, as shown
.background(
GeometryReader { geometry in next.
Color.clear
.onAppear {
size = geometry.size
}
})
Text("\(Int(size.width)) x \(Int(size.height))")
}.padding(100)
}
}

Preferences key's data type, and the value argument is the value we want to
 assign to that key.
onPreferenceChange(Type, perform: Closure)—This modifier
The GeometryReader view sends the information down the hierarchy. Only the executes a closure when the value of a preference key changes. The
views within the GeometryReader view can read and use these values. If we first argument is a reference to the key's data type, and the perform
need to send the values up the hierarchy, we must use Preferences. argument is the closure to be executed when the value changes.
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 The process to pass a value from one view to another begins by defining a
hierarchy. For instance, if we have a Text view inside a VStack view, the structure that conforms to the PreferenceKey protocol. Then, we must apply
preference values generated by the Text view are accessible from the VStack the preference() modifier to a view with the value we want to send. And
view. The values are stored in what is called a Preference Key. This is a finally, we must apply the onPreferenceChange() modifier to a view in the
structure that conforms to the PreferenceKey protocol. The protocol has the hierarchy (a parent view or a container of the previous view) to process the
following requirements. value. The following example illustrates how the process works. We
implement a GeometryReader view to determine the size of an image, as we
Value—This property is an associated type that defines the data type did in the previous example, but send the values to the rest of the views
of the values we are going to work with. using a PreferenceKey structure.
defaultValue—This property defines the value the Preference Key is
going to have by default. Listing 6-64: Setting and reading preferences

reduce(value: Value, nextValue: Closure)—This method adds a import SwiftUI
new value to the structure. The value argument is a reference to the
values stored in the structure from previous calls, and the nextValue struct BoxPreference: PreferenceKey {
typealias Value = CGSize
argument is a closure that returns the new value. static var defaultValue: CGSize = .zero
We must define a structure that conforms to the PreferenceKey protocol and static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
then use it to pass the values from one view to another with the following }
}
modifiers. struct ContentView: View {
@State private var size: CGSize = .zero
preference(key: Type, value: Value)—This modifier sets a value
var body: some View {
for a specific preference key. The key argument is a reference to the VStack {
Image("spot1")
.resizable()
.scaledToFit() As a result, the view always shows the size of the image on the screen,
.background(
GeometryReader { geometry in even when the device is rotated.
Color.clear
.preference(key: BoxPreference.self, value: geometry.size)
}) Figure 6-45: Interface updated with preferences
Text("\(Int(size.width)) x \(Int(size.height))")
}.padding()
.onPreferenceChange(BoxPreference.self) { value in
size = value
}
}
}

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 Do It Yourself: Update the ContentView.swift file with the code in
GeometryProxy structure, we define Value as a typealias of CGSize. Next comes
Listing 6-64. Run the application on the iPhone simulator. Rotate the
a type property called defaultValue. This property defines the value of the screen. You should see the size of the image change according to the
structure by default. Since we are working with a CGSize structure, we orientation.
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 IMPORTANT: In this example, we store a single CGSize value, but the
values already stored in the structure and a closure that returns the new PreferenceKey protocol was designed to manage values from multiple
value. To store this new value, we must execute the closure and add the views and therefore they are usually stored in arrays. For more
value returned to the values already stored in the structure. In this case, information on Preferences, visit our website and follow the links for
we are working only with one value (a CGSize structure), so we assign it this chapter.
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.
The simplest tool provided by SwiftUI to create a list of views is the ForEach The ForEach view adds the content to its container, so we must declare it
view. This view generates a loop that iterates through the values of a inside a container view like a VStack or an HStack, as we did in Listing 7-1. In
collection and creates a new view for each one of them. The structure this example, the loop is defined with a range of integers from 1 to 5, and
includes the following initializer. 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.
ForEach(Data, id: KeyPath, content: Closure)—This initializer The closure receives each value and then creates a Text view with it to show
creates a ForEach view. The first argument is the collection of values it on the screen.
the loop is going to iterate through, the id argument is the key path to
the unique identifier for each value, and the content argument is the Figure 7-1: List of views generated by a ForEach loop
closure that creates the views in each cycle.
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, In this example, the values are presented on the screen with a VStack and
assigning an identifier is easy. These data types conform to the Hashable the configuration by default (center alignment and a width determined by
protocol and therefore they have a hash value that identifies each instance the widest view). Although we can use all the views at our disposal to
(see Listing 3-177). To use this hash value as the identifier, we must specify present each value and apply any styles we want, there is a standard
the \.self key path, as in the following example. design that users immediately recognize in which the values are separated
by a line. SwiftUI includes the following view to create this line.
Listing 7-1: Creating a list of views dynamically

Divider()—This initializer creates a Divider view. The view displays a
struct ContentView: View {
var body: some View { line to separate content.
VStack {
ForEach(1...5, id: \.self) { value in
Text("Value: \(value)") 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
Spacer()
the Divider view in a VStack. 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
Listing 7-2: Creating a list with a standard design purpose, SwiftUI offers the identifiable protocol. The protocol requires the
 structure to define a property called id to store a unique identifier for each
struct ContentView: View { instance, and this is how we prepare the values in our model to work with
let listCities: [String] = ["Paris", "Toronto", "Dublin"]
list of views. For example, the following model creates a structure to store
var body: some View {
VStack {
information about a book, and another to work as the view model for our
ForEach(listCities, id: \.self) { value in application that includes an id property with the unique identifier for each
VStack {
Text(value)
book.
Divider()
} Listing 7-3: Defining a model to work with lists of views
}
Spacer() 
} import SwiftUI
}
}
 struct Book: Hashable {
var title: String
var author: String
The ForEach view in Listing 7-2 creates a list of views from an array of var cover: String
var year: Int
strings. The structure defines the array with the name of three cities and var selected: Bool
then implements the ForEach view to create the list. Notice that String values }
struct BookViewModel: Identifiable, Hashable {
are hashable, so we can also identify them by the hash value with \.self. let id = UUID()
var book: Book
Figure 7-2: List of views with a standard design var title: String {
return book.title.capitalized
}
var author: String {
return book.author.capitalized
}
var cover: String {
return book.cover
 }
var year: String {
return String(book.year)
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 var selected: Bool {
get {
views, as we did in these examples. But when working with data stored by return book.selected
}
set { information about the book (book), computed properties to prepare the
book.selected = newValue
} 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
class ApplicationData: ObservableObject { use a UUID value. This is a structure defined by the Foundation framework
@Published var userData: [BookViewModel]
that guarantees that every time it is created it returns a unique value, so it
init() { is perfect to identify the books.
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1", The observable object is defined with a property called userData to store an
year: 2011, selected: false)),
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover:
array of BookViewModel structures with the information of every book stored
"book2", year: 2017, selected: false)), by the user. And finally, the array is initialized with a total of 12 books to
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)), test the application.
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: IMPORTANT: The Book and BookViewModel structures also conform to
"book5", year: 1996, selected: false)), the Hashable protocol. When a structure conforms to this protocol,
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)), the system creates a unique identifier that is used later to identify
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover: the instances of the structure within collections. Conformance to
"book7", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer", this protocol is required for navigation, as we will see in Chapter 8.
cover: "book8", year: 1982, selected: false)),
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover:
"book9", year: 1993, selected: false)), Once we have the model, we need to decide how are we going to access it
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
1983, selected: false)),
from the views. For small applications, we can store it in a @StateObject
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987, property and pass it to the views one by one, as we did in Chapter 6 (see
selected: false)),
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover: Listing 6-14), but as we will see in Chapter 8, professional applications are
"book12", year: 2007, selected: false)) 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
The Book structure is the actual model. It includes properties to store the introduce to the App structure to inject our model into the environment.
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 Listing 7-4: Injecting the model into the environment

book is currently selected or not. On the other hand, the BookViewModel
import SwiftUI
structure defines the view-model. It includes a property to store all the
@main Spacer()
struct TestApp: App { }
@StateObject var appData = ApplicationData() }
}
struct ContentView_Previews: PreviewProvider {
var body: some Scene {
static var previews: some View {
WindowGroup {
ContentView().environmentObject(ApplicationData())
ContentView()
}
.environmentObject(appData)
}
}
} 
}

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
When the data conforms to the identifiable protocol, the system knows how
closure. The items are instances of the BookViewModel structure, each with
to identify the items, so all we need to do is to declare the collection, and
the information of a book, so we read the structure's properties to show
the list of views is automatically created from the values.
the information on the screen.
Listing 7-5: Creating a list of views with data from the model
Figure 7-3: List of views created from the data in the model

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
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.
will be momentarily pinned to the bounds of the scroll view while the Text(book.year).font(.caption)
}.padding(.top, 5)
content is scrolling. The structure defines the type properties Spacer()
}.padding([.leading, .trailing], 10)
sectionFooters and sectionHeaders. And the content argument is the closure
.padding([.top, .bottom], 5)
that defines the list of views we want to show in the stack. Divider()
}
LazyHStack(alignment: VerticalAlignment, spacing: CGFloat?, }
}
pinnedViews: PinnedScrollableViews, content: Closure)—This }
initializer creates a lazy horizontal stack. The arguments are the same }
}
as the lazy vertical stack, except for the alignment argument, which 
defines the vertical alignment of the views. It is a structure with the
type properties bottom, center, firstTextBaseline, lastTextBaseline, and top. 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
Lazy stacks are similar to the normal stacks created by the VStack and HStack example is to replace the VStack views implemented before with a
views, with the difference that the views in a lazy stack are created as LazyVStack and embed the list in a SrollView view. Now the user can scroll the
needed. The system takes care of creating the views only before they are list of books.
about to be rendered onscreen, consuming less resources and improving
performance. Other than the use of lazy stacks, the list is defined as Figure 7-4: Scroll view
before, but embedded in a ScrollView view.
 needs to define its content. In this case, we pass a copy of the BookViewModel
struct ContentView: View { structure processed by the loop.
@EnvironmentObject var appData: ApplicationData
The application works the same way as before, but because of the changes
var body: some View { we have introduced to the main view, the books are displayed side by side.
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 0) {
Figure 7-5: Horizontal scroll view
ForEach(appData.userData) { book in
CellView(book: book)
}
}
}
} 
}
struct CellView: View {
let book: BookViewModel 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
var body: some View {
VStack {
following view for this purpose.
Image(book.cover)
.resizable()
.scaledToFit()
ScrollViewReader(content: Closure)—This initializer creates a
.frame(width: 80, height: 100) container view to allow programmatic scrolling on a ScrollView view.
Text(book.title)
.font(.caption) The content argument is a closure with the ScrollView view we want to
}.padding(10) control.
.frame(width: 100, height: 150)
}
}
The ScrollViewReader view creates a structure of type ScrollViewProxy with the

information required to scroll the list of views to a specific row. For this
In this example, we tell the ScrollView view to create a horizontal view purpose, the structure includes the following method.
without indicators, and then place the views inside a LazyHStack to display
scrollTo(Value, anchor: UnitPoint?)—This method scrolls the list
the list horizontally.
of views to the view identified with the value specified by the first
Notice that we have also moved the code that creates the views for each
argument. The anchor argument determines the position of the view
item to a separate view called CellView (These views represent the cells or
after the scrolling is over. It is a structure that we can create from the
rows on the list). This is not required but is recommended because it
type properties bottom, bottomLeading, bottomTrailing, center, leading, top,
improves the workflow and simplifies the code. The only thing we need to
topLeading, topTrailing, trailing, and zero.
remember is that the view we use to build the row doesn't have access to
the values processed by the main view, so we must pass the data this view
The scrollTo() method scrolls the list to make the view with the specified }.padding()
}
identifier visible. The ForEach view automatically assigns an identifier to the }
views on the list (in our case, this is the UUID value assigned to the id }
}
property), but the scrollTo() method requires the identifier to be declared }
explicitly. The framework includes the following modifiers for this purpose. 
id(Value)—This modifier assigns a new identifier to the view. The This ContentView view replaces the same view from the previous example. It
argument is the Hashable value we want to use to identify the view. creates a horizontal scrollable list of books, but this time the ScrollView view
tag(Value)—This modifier assigns an identifier to the view. The is embedded in a ScrollViewReader view so we can control it from code. To be
argument is a Hashable value, usually defined with an integer. 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
To be able to scroll the ScrollView view programmatically, we must embed it property as the view's identifier. Below the ForEach view, we include a Button
in a ScrollViewReader view and use the proxy value provided by this view to view to let the user scroll the list to the beginning. For this purpose, we get
call the scrollTo() method when necessary. And for this method to work, we the first item in the userData array, read the id property, and call the scrollTo()
need to identify the views with the id() or tag() modifiers. These modifiers method on the proxy with this value. As a result, when the user scrolls the
work the same way in most circumstances, but for ScrollView views is list to the end, a button appears, and if pressed, it scrolls the list back to
recommended to identify the rows with the id() modifier, as in the following the beginning.
example.
Figure 7-6: Custom scroll
Listing 7-9: Scrolling the list programmatically

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData

var body: some View {
ScrollViewReader { proxy in
ScrollView(.horizontal, showsIndicators: false) { Do It Yourself: Update the ContentView view from the project created
LazyHStack(spacing: 0) { in the previous section with the code in Listing 7-8. You should see
ForEach(appData.userData) { book in
CellView(book: book) something like Figure 7-5. Update the ContentView view with the code
.id(book.id) in Listing 7-9. Scroll the list to the end. You should see the Go Back
}
Button("< Go Back") { button, as illustrated in Figure 7-6. Press the button to scroll the list
if let firstIdentifier = appData.userData.first?.id {
proxy.scrollTo(firstIdentifier, anchor: .top) back to the beginning.
}
Lazy Grids

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.
ForEach(appData.userData) { book in
Image(book.cover)
Griditem(Size, spacing: CGFloat?, alignment: Alignment?)— .resizable()
This structure defines the size, padding, and alignment of each item .scaledToFit()
}
on the grid. The first argument is an enumeration that includes three }
}.padding()
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 In this example, we define an array of three GridItem structures, all with a
properties bottom, bottomLeading, bottomTrailing, center, leading, top, fixed size of 75 points, and use it to define a vertical grid of books. Because
topLeading, topTrailing, and trailing. we use a fixed size, and include three instances of the structure, the
LazyVGrid view creates a grid with three books per row, of a size of 75
With a GridItem structure we can determine the number of views we want points each, no matter the space available.
to display per row or column, or if we want the grid to determine the
number of items to show depending on the space available. For this Figure 7-7: Fixed grid
purpose, the structure can take three different associated values. The fixed()
and flexible() values define the fixed or flexible size of a single view, and the
adaptive() value defines the flexible size of multiple views. For instance, if we
want to create a vertical grid with a specific number of items per row and a
fixed size, we must create an array of GridItem structures with the fixed()
value. The number of instances of the GridItem structure included in the
array determine the number of items per row, as shown next.
Listing 7-10: Creating a grid with a fixed number of items 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
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
the space available, we can turn the items flexible with the flexible() value,
as shown next.
let guides = [
GridItem(.fixed(75)), Listing 7-11: Defining flexible items
GridItem(.fixed(75)),
GridItem(.fixed(75)) 
] let guides = [
var body: some View {
ScrollView { GridItem(.flexible(minimum: 75), alignment: .top),
LazyVGrid(columns: guides) { GridItem(.flexible(minimum: 75), alignment: .top),
GridItem(.flexible(minimum: 75), alignment: .top) number of items that fit inside a row according to the space available. If
] there is any space left, it expands the items to keep the same space in

between.
The flexible() value still represents one single item, so we still must include
Figure 7-9: Adaptive grid
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.)
Presenting information in a scrollable list of rows and columns is a common Listing 7-13: Creating a list of views with the List view
requirement of any application. For this reason, SwiftUI includes an 
additional view to create lists of values called List. Like a LazyVStack view, a struct ContentView: View {
List view creates a vertical list with a single column of items,, but the items @EnvironmentObject var appData: ApplicationData
are automatically separated by a line, and the view includes built-in
var body: some View {
functionality to select, add, or remove content. The following are some of List(appData.userData) { book in
the view's initializers. CellBook(book: book)
}
}
List(Data, rowContent: Closure)—This initializer creates a list of }
struct CellBook: View {
views. The first argument is the collection of values to create the let book: BookViewModel
rows. These values must conform to the identifiable protocol and
var body: some View {
implement the id property to provide a unique identifier. The HStack(alignment: .top) {
rowContent argument is a closure that defines the views used to Image(book.cover)
.resizable()
create the rows. .scaledToFit()
.frame(width: 80, height: 100)
List(Data, id: KeyPath, rowContent: Closure)—This initializer VStack(alignment: .leading, spacing: 2) {
creates a list of views. The first argument is the collection of values to Text(book.title).bold()
Text(book.author)
create the rows, the id argument is the key path to the unique Text(book.year).font(.caption)
Spacer()
identifier of each value, and the rowContent argument is a closure }.padding(.top, 5)
that defines the views used to create the rows. Spacer()
}
List(Data, selection: Binding, rowContent: Closure)—This }
}
initializer creates a list of views. The first argument is the collection of 
values to create the rows, the selection argument is a Binding property
that stores one or a set of identifiers to recognize the selected rows, Like the ForEach view, the List view in Listing 7-13 reads the values in the
and the rowContent argument is a closure that defines the views used model and creates the rows with the views defined by the closure. In this
to create the rows. 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.
the sidebar style adds a background to the whole list. The following example
Figure 7-10: List of rows created by a List view applies a plain styling to the list.
In mobile devices, the list is shown with a style that generates a padding Figure 7-11: Plain list
around the views and includes a background color. The View protocol
defines the following modifier to customize the style.
CellBook(book: book) Listing 7-17: Mixing static and dynamic content in a list
.listRowBackground(index % 2 == 0 ? colors[0] : colors[1])
.listRowSeparator(.hidden) 
}.listStyle(.plain) struct ContentView: View {
} @EnvironmentObject var appData: ApplicationData
}

var body: some View {
List {
The view in Listing 7-16 defines a property called colors with the two colors HStack {
Image(systemName: "book.circle")
we want to assign to the views (white and gray). In the closure assigned to .font(.largeTitle)
the List view, we get the index of the book with the firstIndex() method. The Spacer()
Text("My Favorite Books")
method looks for an item in the array which id property has the same value .font(.headline)
than the id property of the current book, and returns the index of the item }.frame(height: 50)
ForEach(appData.userData) { book in
if found, or nil otherwise. To make sure that we always get a value, we use CellBook(book: book)
the nil-coalescing operator (??) to return the index 0 if no value is found. }
}.listStyle(.plain)
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 
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.
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.
ForEach(sections.value) { book in
CellBook(book: book)
}
}.headerProminence(.increased)
}
}
}
}


Before defining the body property, the view in Listing 7-21 defines a
computed property that returns an array of tuples with the books
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 organized by the title's first letter. First, the closure applies the
second section contains the list of books. But sections also come in handy Dictionary(grouping:, by:) initializer to create a dictionary from the values of the
when we need to separate the content in groups, like movies in categories, userData array (the list of books in the model). The way this initializer works
or organize items alphabetically. For this purpose, we must prepare the is that for every value in the array, it executes a closure and then groups
data according to the organization we want to present to the user. An the results by the values returned by the closure (see Listing 3-122). In this
alternative is to define a computed property in the view that returns the example, we get the index of the first character in the book's title with the
list of books in alphabetical order.
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
Listing 7-21: Providing an ordered list of values
tuples which first value is a letter of the alphabet and the second value is

struct ContentView: View { an array with all the books which titles begin with that letter ([(key: String,
@EnvironmentObject var appData: ApplicationData value: [BookViewModel])]). After this array is created, we sort the values
alphabetically with the sorted() method and return it, so every time the
var orderList: [(key: String, value: [BookViewModel])] {
views read the orderList property, they get an array of tuples with the books
let listGroup: [String: [BookViewModel]] = Dictionary(grouping: appData.userData, by: { value
in sorted alphabetically.
let index = value.title.startIndex
let initial = value.title[index]
The values of the tuples were identified with the labels key and value, so we
return String(initial) can use these labels to read them and create our list. In this example, we
})
return listGroup.sorted(by: { $0.key < $1.key }) 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
var body: some View {
List { ForEach loop reads the content of the orderList property and creates a
ForEach(orderList, id: \.key) { sections in section with the value identified by the key label (the letter). Inside the
Section(header: Text(sections.key)) { 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 Edit Mode
organized alphabetically into sections. 
Figure 7-18: Alphabetical sections Listviews also provide tools to work with the values on the list. The
following are the modifiers available to remove and sort views.
move(fromOffsets: IndexSet, toOffset: Int)—This method moves on the userData array to remove the item from the model. Now the user can
the items of the array in the indexes specified by the fromOffsets delete the book by dragging the row to the left and pressing the Delete
argument to the index determined by the toOffset argument. button.
The onDelete() and onMove() modifiers identify the items by the indexes in the Figure 7-19: Automatic delete feature
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.
Do It Yourself: Update the ContentView view with the code in Listing 7-
Listing 7-22: Deleting rows
22. Drag a row to the left and press the Delete button, as shown in

Figure 7-19. The row should be removed from the list.
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
Besides the possibility to drag a row to delete it, List views also include a
var body: some View { set of tools that allow the user to select, remove, and move rows. The tools
List {
ForEach(appData.userData) { book in are displayed to the user when the list is in edit mode. The easiest way to
CellBook(book: book)
activate this mode is with the EditButton view. This view creates a button
}.onDelete { indexes in
appData.userData.remove(atOffsets: indexes) that activates and deactivates the edit mode when pressed, as shown next.
}
} Listing 7-23: Activating the edit mode
}

}
 struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
Notice that the modifiers are implemented by the ForEach view, so we need
var body: some View {
to create the list with this view. In the example of Listing 7-22, we use the VStack {
EditButton()
onDelete() modifier. The closure assigned to the modifier receives an IndexSet
List {
value with the index of the row to be deleted and calls the remove() method ForEach(appData.userData) { book in
CellBook(book: book) ForEach(appData.userData) { book in
}.onDelete { indexes in CellBook(book: book)
appData.userData.remove(atOffsets: indexes) }.onDelete { indexes in
} appData.userData.remove(atOffsets: indexes)
}.listStyle(.plain) }
} .onMove { source, destination in
} appData.userData.move(fromOffsets: source, toOffset: destination)
} }
 }.listStyle(.plain)
}
}
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
The system detects that we have implemented the onMove() modifier and
instance, in our example, we included the onDelete() modifier, so the view
automatically provides the tools for the user to move the rows and
exposes buttons to let the user delete the row.
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
Figure 7-20: Edit button
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.
The tool to move rows is included when the onMove() modifier is applied to
the ForEach view, as in the following example.
The edit mode also allows the user to select one or more rows. For single When working with structures that conform to the Identifier protocol, the
selection, the mode is enabled by default. All we need to do is to initialize List view automatically identifies the views with the values assigned to the
the List view with the selection argument. The argument takes a Binding id property. In our example, these are the UUID values assigned to each
property to store the identifier of the selected row, which we can use later book. When a row is selected, the List view assigns this identifier to the
to process the item, as shown next. @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
Listing 7-25: Selecting a row
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
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData BookViewModel.ID we make sure that the data type assigned to the property
@State private var selectedRow: BookViewModel.ID? = nil
is the same assigned to the id property, no matter if later we replace this
var body: some View { identifier with another one.
VStack { If the user selects an item, the List view assigns the value of the item's id
HStack {
Spacer() property to the selectedRow property, so we can do whatever we want with
Button(action: {
removeSelected()
it. In this case, we add a Button view at the top that calls a method to
}, label: { remove the selected book. The method gets the index of the selected item
Image(systemName: "trash")
in the userData array, calls the remove() method to remove it, and assigns the
}).disabled(selectedRow == nil ? true : false)
}.padding() 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
List(selection: $selectedRow) { book has been selected.
ForEach(appData.userData) { book in
CellBook(book: book)
} Figure 7-22: Single selection
}.listStyle(.plain)
}
}
func removeSelected() {
if let index = appData.userData.firstIndex(where: { $0.id == selectedRow }) {
appData.userData.remove(at: index)
selectedRow = nil
}
}
} 

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 func removeSelected() {
var indexes = IndexSet()
can store the identifiers in an IndexSet structure and apply the same for item in selectedRows {
methods used above to move and remove rows. The IndexSet structure if let index = appData.userData.firstIndex(where: { $0.id == item }) {
indexes.insert(index)
provides the following method for this purpose. }
}
appData.userData.remove(atOffsets: indexes)
insert(Int)—This method adds the index specified by the argument selectedRows = []
to the set. }
}

In the following example, we modify the @State property to take a Set of
UUID values instead of just one (Set<BookViewModel.ID>), and create an IndexSet The view now includes two views at the top: the EditButton view to activate
structure with the indexes of the selected rows to remove the values from or deactivate the edit mode, and the trash can button to remove the books
the model. 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
Listing 7-26: Selecting multiple rows 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,
struct ContentView: View { the method creates an empty IndexSet structure and then iterates through
@EnvironmentObject var appData: ApplicationData
@State private var selectedRows: Set<BookViewModel.ID> = [] 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()
var body: some View {
VStack { method on the userData array to erase the books, and then clean the
HStack { selectedRows property to allow the user to start the process again.
EditButton()
Spacer()
Button(action: { Figure 7-23: Selected rows
removeSelected()
}, label: {
Image(systemName: "trash")
}).disabled(selectedRows.count == 0 ? true : false)
}.padding()
List(selection: $selectedRows) {
ForEach(appData.userData) { book in
CellBook(book: book) 
}
}.listStyle(.plain)
}
}
Do It Yourself: Update the ContentView view with the code in Listing 7- removeSelected()
}, label: {
26. Press the Edit button. Select a row and click the button on the Image(systemName: "trash")
}).disabled(selectedRows.count == 0 ? true : false)
right. The row should be removed, as shown in Figure 7-23. }.padding()
If we want to deactivate the edit mode after books are removed, or after List(selection: $selectedRows) {
any other action for that matter, we must control the mode ForEach(appData.userData) { book in
CellBook(book: book)
programatically. The environment offers the following property for this }
purpose. }.listStyle(.plain)
.environment(\.editMode, .constant(editActive ? EditMode.active : EditMode.inactive))
}
editMode—This property defines the state of the edit mode for the }
func removeSelected() {
view. It is a Binding property of type EditMode, an enumeration with the var indexes = IndexSet()
for item in selectedRows {
values active, inactive, and transient. The enumeration also includes the if let index = appData.userData.firstIndex(where: { $0.id == item }) {
isEditing property to return a Boolean value that indicates whether the indexes.insert(index)
}
view is in edit mode or not. }
appData.userData.remove(atOffsets: indexes)
selectedRows = []
To manage the mode, we need a @State property with a Boolean value that editActive = false
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. 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
Listing 7-27: Customizing the edit mode 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
struct ContentView: View { books are deleted, and to set the title for the button ("Done" if true, "Edit"
@EnvironmentObject var appData: ApplicationData
@State private var selectedRows: Set<BookViewModel.ID> = [] if false). Notice that the environment's editMode property is a Binding property
@State private var editActive: Bool = false and therefore we had to use the constant() method to provide a Binding value
of type EditMode (see Listing 6-11).
var body: some View {
VStack { The result is the same as before, but the process is now customized and
HStack {
Button(editActive ? "Done" : "Edit") { therefore we can change the edit mode from code anytime we want by
editActive.toggle() assigning the value true or false to the editActive property.
}
Spacer()
Button(action: {
Do It Yourself: Update the ContentView view with the code in Listing 7- let book: BookViewModel
27. Press the Edit button. Select a row and click the Remove button.
var body: some View {
The row should be removed, as before, but now the edit mode is HStack(alignment: .top) {
Image(book.cover)
automatically deactivated. .resizable()
.scaledToFit()
.frame(width: 80, height: 100)
The selection process can also be customized, but it requires us to keep VStack(alignment: .leading, spacing: 2) {
track of the selected rows ourselves and detect the selection with the Text(book.title).bold()
Text(book.author)
onTapGesture() modifier. We have implemented this modifier before to detect Text(book.year).font(.caption)
Spacer()
when an image was tapped by the user (see Listing 6-49). We will study }.padding(.top, 5)
gestures in Chapter 12, but for our purpose all we need to know is that the Spacer()
if selected == book.id {
modifier executes a closure when the user taps the view. In our case, we Image(systemName: "checkmark")
need to apply it to every CellBook view to assign the value of the id property .foregroundColor(Color.green)
.frame(width: 25, height: 25)
of the selected book to a @State property, as shown next. }
}
}
Listing 7-28: Customizing the selection }


struct ContentView: View {
@EnvironmentObject var appData: ApplicationData In this example, we allow the selection of one row at a time. If the user
@State private var selectedRow: BookViewModel.ID? = nil
taps on a row, the onTapGesture() modifier checks whether the book's id was
var body: some View { already stored in the @State property. If it was, it deselects the row by
List { assigning the value nil to the property, otherwise, the value of the book's id
ForEach(appData.userData) { book in
CellBook(selected: $selectedRow, book: book)
property is assigned to the @State property to indicate that the row was
.background(.white) selected. To show the selection to the user, we connect the @State property
.onTapGesture {
if selectedRow == book.id {
with a @Binding property in the CellBook view and then show an image if the
selectedRow = nil value of this property is equal to the book's identifier. As a result, a
} else {
selectedRow = book.id checkmark is shown on the row when it is selected.
}
}
} Figure 7-24: Custom selection
}.listStyle(.plain)
}
}
struct CellBook: View {
@Binding var selected: BookViewModel.ID?

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
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.
}, label: {
Image(systemName: "trash")
})
}
}
}.listStyle(.plain)
}
func removeBook(book: BookViewModel) {
var indexes = IndexSet()
if let index = appData.userData.firstIndex(where: { $0.id == book.id }) { 
indexes.insert(index)
}
appData.userData.remove(atOffsets: indexes)
}
Do It Yourself: Update the ContentView.swift file with the code in
} Listing 7-30. Drag a row to the left, and press the trash can button.
struct CellBook: View {
let book: BookViewModel The row should be deleted. You will notice that the process of
removing the row is not animated. We will learn how to create
var body: some View { animations in Chapter 11.
HStack(alignment: .top) {
Image(book.cover)
.resizable()
.scaledToFit()
.frame(width: 80, height: 100)
VStack(alignment: .leading, spacing: 2) {
Text(book.title).bold()
Text(book.author)
Text(book.year).font(.caption)
Spacer()
}.padding(.top, 5)
Spacer()
}
}
}

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.
Button(action: {
removeBook(book: book)
Refreshable When the user scrolls down the list, the closure is executed to perform the
task. In this example, we print a message on the console, but the task
 usually involves accessing a server or a database to download and process
information. (We will learn how to perform these processes later.)
There is a useful feature, usually provided by modern applications, that
allows users to refresh the data by scrolling down the list. When the user Figure 7-27: Refresh control
keeps scrolling down, a spinning wheel appears at the top of the screen to
indicate that the system is refreshing the data. The View protocol includes
the following modifier to add this feature to a List view.
functionality is provided by the List view, but the information in the model
var body: some View {
must be organized accordingly. For instance, the following model includes a List(contentData.items, children: \.options) { item in
Text(item.name)
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 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
import SwiftUI structures). In this example, we define two parent items called "Food" and
"Beverages", each with their own children assigned to the options property.
struct MainItems: Identifiable {
var id = UUID() There is one item called "Cheese" with three more children, creating an
var name: String!
var options: [MainItems]! additional hierarchy. When the user taps on the "Food" or "Beverages"
items, all the items stored in their options property are displayed, and the var body: some View {
List {
same happens whit the "Cheese" item. ForEach(contentData.items) { section in
Section(header: Text(section.name)) {
OutlineGroup(section.options ?? [], children: \.options) { item in
Figure 7-28: Hierarchical list Text(item.name)
}
}
}
}.listStyle(.sidebar)
}
}

 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",
The List view implements another view in the background called that we use to create the sections with a Section view. To display the content
OutlineGroup to create the hierarchical list of views, but we can declare this
of the sections, we use an OutlineGroup view. This view takes the values in
view ourselves to create more complex hierarchies. The OutlineGroup view the options property of each item and creates an outline list. This creates a
includes the following initializer. 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
OutlineGroup(Data, children: KeyPath, content: Closure)—This accessory and become expandable.
view creates a hierarchical list. The first argument is the data used to
create the views, the children argument is a key path to the property Figure 7-29: Custom hierarchy
that contains the data to create the children view, and the content
argument is the closure with the views for each row.
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.
Do It Yourself: Create a Multiplatform project. Update the
Listing 7-34: Implementing an OutlineGroup view ContentView.swift file with the code in Listing 7-33. You should see
 the interface illustrated in Figure 7-28. Update the ContentView view
struct ContentView: View { with the code in Listing 7-34. Now you should see the interface
@ObservedObject var contentData = ContentViewData()
illustrated in Figure 7-29.
7.3 Tables

views were designed for the small screen of mobile devices like iPhones
List
and Apple Watches. iPads and Macs have a larger screen and therefore
more space to display content. If we only need one column of values, we
can use a List view, as we have done so far, but to present more columns
SwiftUI includes the Table view. The following is the view's initializer.
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 Something similar happens with the TableColumn view. For instance, if the
column. 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,
width(min: CGFloat?, ideal: CGFloat?, max: CGFloat?)—This
we must provide the closure to the content argument, format the value,
modifier defines a flexible column but with constraints.
and create the view we want to use to show it.
The following example shows a possible implementation. The Table view is
As always, we need a model with some data to test the app. The following
defined only with the data source (the listOfItems property) and a closure to
includes a structure with a few properties to provide the values for the
create the columns. Some columns use a key path to provide the values
columns, a @Published property to provide the values to the views, and
and the last one a closure with a Text view.
some data for testing.
Listing 7-36: Creating a table with multiple columns
Listing 7-35: Defining a model to test tables


struct ContentView: View {
import SwiftUI @EnvironmentObject var appData: ApplicationData
 VStack {
EditButton()
The Table view requires the sort comparators to be stored in a Binding Table(appData.listOfItems, selection: $selectedItems) {
TableColumn("Name", value: \.name)
property so it can select which one to use according to the action TableColumn("Category", value: \.category)
performed by the user. In this example, we define a @State property with TableColumn("Calories") { item in
Text("\(item.calories)")
two sort comparators to allow the user to sort the items by name and }.width(100)
calories. Next, we define a computed property called sortedItems that sorts }
Text(listSelected())
the items according to these sort comparators and returns the list of values .padding()
for the view. }
}
Notice that we have added a key path to the Calories column. The value is func listSelected() -> String {
produced by the closure, but the key path is still required to tell the table let list: [String] = selectedItems.map({ id in
let item = appData.listOfItems.first(where: { $0.id == id })
which column to sort. return item?.name ?? ""
})
return String(list.sorted().joined(separator: " "))
Do It Yourself: Update the ContentView view with the code in Listing 7- }
37. Run the application on the iPad simulator or the Mac (My Mac). }

Click on the header of the Name and Calories columns. You should
see the items sorted by name or calories, respectively. 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
Tables also allow the user to select one or multiple rows. The process is the the EditButton view. To show the names of the selected items, we created a
same used before for List views. We must provide a @State property to store method that maps the identifiers, gets the items from the listOfItems array,
the identifiers of the items selected by the user and assign that property to returns the value of the name property, and then sorts and joins the values
the table. For single selection, everything works out of the box, but iPads to create a string with all the names.
without a keyboard require the edit mode to be enabled. In the following In this example, we display the selected names on the screen, but of
example, we include an EditButton view to allow the user to enable this course we can add buttons to the interface to process the values as we
mode and also a Text view below to show the names of the items selected need. A useful tool provided by tables for this purpose are context menus.
by the user. 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
Listing 7-38: Allowing the user to select items in a table menu.

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
contextMenu(forSelectionType: Type, menu: Closure)—This
@State private var selectedItems: Set<ConsumableItem.ID> = [] modifier assigns a context menu to an item or multiple items of a
view. The forSelectionType argument specifies the data type of the
var body: some View {
values we want to associate with the menu, and the menu argument }
}
provides the options for 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 If there are no items in the set, which means that the user right-clicked on
multiple selected rows, or the table's empty area. All we need to do to an empty area of the table, we show a button to add a new item to the
configure the menu is to count how many items are in the array, as shown model. If there is one item, we offer a button to remove the value from the
next. 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
Listing 7-39: Using a context menu to process a row adapts based on where the user right-clicks (taps and holds on iPads).

struct ContentView: View { Do It Yourself: Update the ContentView view with the code in Listing 7-
@EnvironmentObject var appData: ApplicationData
@State private var selectedItems: Set<ConsumableItem.ID> = []
39. Run the application on the iPad simulator or the Mac (My Mac).
Right click on a row (Click and hold on the iPad simulator). Select the
var body: some View { "Remove Item" option. The item should be removed. Select two or
Table(appData.listOfItems, selection: $selectedItems) {
TableColumn("Name", value: \.name) more rows and repeat the process to remove them. Do the same but
TableColumn("Category", value: \.category) in the table's empty area below and select the option "Create New
TableColumn("Calories") { item in
Text("\(item.calories)") Item". You should see a new item with the name "Test" at the
}.width(100)
} bottom of the list. In this example, we always add the same item
.contextMenu(forSelectionType: ConsumableItem.ID.self, menu: { selected in with the name "Test". Later we will learn how to open additional
if selected.count <= 0 {
Button("Create New Item") { views to allow the user to insert custom values.
let newItem = ConsumableItem(name: "Test", category: "Test", calories: 0, included:
false)
appData.listOfItems.append(newItem) 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
} else if selected.count == 1 {
Button("Remove Item") { as a checkbox on the Mac, to allow the user to check or uncheck the items.
appData.listOfItems.removeAll(where: { item in
item.id == selected.first
}) Listing 7-40: Defining the content of a column with a Toggle view
} 
} else {
Button("Remove Selected Items") { struct ContentView: View {
appData.listOfItems.removeAll(where: { item in @EnvironmentObject var appData: ApplicationData
selected.contains(item.id)
}) var body: some View {
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.
SwiftUI also provides pickers to present lists of values. A picker can show The Picker view creates a general-purpose picker to show a list of values.
the values in a graphic that simulates a wheel, on a list, or as a row of The structure includes the following initializer.
buttons, depending on the platform and the configuration. The framework
includes two views for this purpose, one to create a general-purpose list, Picker(String, selection: Binding, content: Closure)—This
and another to generate a list of dates and times. initializer creates a Picker view with the label defined by the first
argument. The selection argument is a Binding property that stores the
value selected by the user, and the closure assigned to the content
argument provides the list of views required to present 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.
Figure 7-32: Menu picker 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.
let listCities: [String] = ["Paris", "Toronto", "Dublin"] these values, the structures include the type properties automatic, menu,
segmented, and wheel.
var body: some View {
VStack {
Text(listCities[selectedValue]) The automatic style allows the picker to adapt the design to the device and
Picker("Cities:", selection: $selectedValue) {
conditions, and the menu style create a context menu, as we have seen in
ForEach(listCities.indices, id: \.self) { value in
Text(listCities[value])
the previous examples. But there are two additional styles. The wheel style
.tag(value) 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
Spacer() into buttons. The latter is frequently used to allow the user to select a
}.padding()
} value from a limited set of options, as illustrated by the following example.
}

Listing 7-46: Implementing a segmented picker
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 struct ContentView: View {
@State private var selectedValue: String = "No Value"
each value, we define the ForEach view with the Range returned by the indices let listCities: [String] = ["Paris", "Toronto", "Dublin"]
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 var body: some View {
VStack {
the tag() modifier to the selectedValue property, and therefore we can use this Text(selectedValue)
property to get the name of the city from the array. Picker("Cities:", selection: $selectedValue) {
ForEach(listCities, id: \.self) { value in
Text(value)
Do It Yourself: Update the ContentView view with the code in Listing 7- }
}.pickerStyle(.segmented)
45. The application should work as before, but now the values
Spacer()
processed by the picker are integers instead of strings. }.padding()
}
}
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 implementation is the same, but by applying a custom style we change
the way the values are presented to the user. Below are the different
pickerStyle(Style)—This modifier sets the style of the picker. The pickers we see when the style is set to wheel or segmented.
argument is a structure that conforms to the PickerStyle protocol. The
system defines a few structures with predefined styles. To declare Figure 7-33: Wheel and segmented pickers
Date Pickers

Listing 7-47: Defining a DatePicker to select a date times. You can also include the labelsHidden() modifier to remove the
 label.
struct ContentView: View {
@State private var selectedDate: Date = Date()
By default, the DatePicker view presents a list with all the dates from a
var body: some View { distant date in the past to a distant date in the future. To limit the list of
VStack { values the user can select from, we must provide a range of dates. The
DatePicker("Date:", selection: $selectedDate)
Spacer() range can be closed (from one date to another), or open. The following
}.padding() example shows a list of dates from the current date to a distant date in the
}
} future using an open range.

Listing 7-48: Limiting the DatePicker to a range of values
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 struct ContentView: View {
@State private var selectedDate: Date = Date()
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 var body: some View {
shows buttons to open a calendar to select the date and a spinning wheel VStack {
DatePicker("Date:", selection: $selectedDate, in: Date()..., displayedComponents: .date)
to select the time. Spacer()
}.padding()
}
Figure 7-34: Picker for dates }

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
Do It Yourself: Update the ContentView view with the code in Listing 7- in which it is presented, but it includes the following modifier to specify
47. You should see the interface in Figure 7-34, left. Add the the style we want.
displayComponents argument to the DatePicker view initializer with
the value date or hourandminute to allow users to select only dates or datePickerStyle(Style)—This modifier sets the style of the picker.
The argument is a structure that conforms to the DatePickerStyle
protocol. To provide standard styles, the system defines a few
structures with the type properties automatic, compact, graphical, and
wheel.
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. 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
Listing 7-49: Presenting a picker with a spinning wheel values.

struct ContentView: View {
@State private var selectedDate: Date = Date()
Listing 7-50: Picking multiple dates

var body: some View { struct ContentView: View {
VStack { @State private var selectedDates: Set<DateComponents> = []
Text("Date: \(selectedDate.formatted(.dateTime.day().month()))") @State private var mydates: String = ""
DatePicker("Date:", selection: $selectedDate, displayedComponents: .date)
.labelsHidden()
var body: some View {
.datePickerStyle(.wheel) VStack {
Spacer()
MultiDatePicker("Dates:", selection: $selectedDates)
}.padding()
Spacer()
}
Text(mydates)
}
}.padding()

.onChange(of: selectedDates) { values in
let days = values.map({ value in String(value.day!) })
In addition to assigning the wheel style to the picker, the view in Listing 7- mydates = days.joined(separator: ",")
}
49 also includes a Text view to show the day and month selected by the }
user. The result is shown below. }

Figure 7-35: Wheel picker 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.
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.
Most views allow us to specify a string that the Form view can use to create
Listing 7-51: Defining a form 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.
struct ContentView: View { To create custom labels, SwiftUI includes the LabeledContent view. This view
@State private var setActive: Bool = false
@State private var setShowPictures: Bool = false attaches a label to another view. The following is the most frequently used
@State private var setTotal: Int = 10 initializer.
var body: some View {
Form { LabeledContent(String, content: Closure)—This initializer
Toggle("Active", isOn: $setActive)
Toggle("Show Pictures", isOn: $setShowPictures)
attaches a label to a view. The first argument specifies the label and
HStack { the content argument provides the views.
Text("Total")
Spacer()
Text(String(setTotal)) With this view, we can better organize our previous example and provide a
Stepper("", value: $setTotal, in: 0...10) custom label for the Stepper view.
.labelsHidden()
}
} Listing 7-52: Defining a custom label
}
} 
 struct ContentView: View {
@State private var setActive: Bool = false
@State private var setShowPictures: Bool = false
The Form view adds padding on the sides of the controls and present the @State private var setTotal: Int = 10
list with a grouped style (see Listing 7-14).
var body: some View {
Form {
Figure 7-37: Simple form Toggle("Active", isOn: $setActive)
Toggle("Show Pictures", isOn: $setShowPictures)
LabeledContent("Total") {
Text(String(setTotal))
Stepper("", value: $setTotal, in: 0...10)
.labelsHidden()
}
}
}
}

A Form view works like a List view, so we can separate the content in
sections to provide a more standard design.

Listing 7-53: Styling a form
Do It Yourself: Create a Multiplatform project. Update the ContentView

struct ContentView: View {
view with the code in Listing 7-53. You should see the interface in
@State private var setActive: Bool = false Figure 7-38. Add controls to the Form view and create more sections
@State private var setShowPictures: Bool = false
@State private var setTotal: Int = 10 to see how they look like.
var body: some View { IMPORTANT: In these examples, we have stored the values locally
Form {
with @State properties. This means that the values inserted by the
Section(header: Text("Options"), footer: Text("Activate the options you want to see")) {
Toggle("Active", isOn: $setActive) user are going to be available only for this view. To make them
Toggle("Show Pictures", isOn: $setShowPictures) available to the rest of the app, we can store them in the model with
}
@Published properties, or send them to other views, as we will see in
Section(header: Text("Values"), footer: Text("Insert the number of items to display")) {
LabeledContent("Total") {
the next chapter.
Text(String(setTotal))
Stepper("", value: $setTotal, in: 0...10)
.labelsHidden()
}
}
}
}
}

initializer.
When the views are collapsed, all we see is the view's label. If we tap on
DisclosureGroup(String, isExpanded: Binding, content: these labels or the disclosure indicator, the content is expanded and we
can interact with the controls.
Closure)—This initializer creates a DisclosureGroup view to hide or show
content. The first argument is the view's label, the isExpanded
Figure 7-39: Disclosure groups
argument is a Binding property of type Bool that determines whether
the view is expanded or collapsed, and the content argument is the
closure that provides the views we want to show or hide.
With this view, we can organize the two sections of our form in expandable
lists.

Listing 7-54: Disclosing controls

struct ContentView: View {
@State private var setActive: Bool = false
@State private var setShowPictures: Bool = false
@State private var setTotal: Int = 10

Navigation Stack 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).
The ContentView view included in the Multiplatform template represents the navigationBarBackButtonHidden(Bool)—This modifier hides the
app's initial view, but we are responsible for creating the rest. The way Back button that is automatically generated by the NavigationStack view
these additional views are defined is always the same, we declare a to navigate to previous views.
structure that conforms to the View protocol and implement the body statusBarHidden(Bool)—This modifier hides the status bar for all
property, but there are different mechanisms to present them to the user. the views in the navigation hierarchy.
The one recommended by Apple creates a transition by moving the views
from right to left, as illustrated in Figure 8-1. If the user wants to go back to A NavigationStack view creates a stack of views with the views opened by the
the previous view, the current view is removed with a transition in the user. Therefore, to start this navigation hierarchy we need to embed the
opposite direction. To create this navigation system, SwiftUI defines the content of our app's initial view in a NavigationStack view, as we do in the
NavigationStack view. The following is the structure's initializer. following example.
NavigationStack(path: Binding, root: Closure)—This initializer Listing 8-1: Initiating a navigation stack
creates a navigation stack to manage the views the user can navigate 
through. The path argument is the Binding property that stores the struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
navigation path the user has followed, and the root argument is a
closure that returns the root view (the first view presented by the var body: some View {
NavigationStack {
navigation stack).
List(appData.userData) { book in
CellBook(book: book)
}.navigationTitle(Text("Books"))
A NavigationStack view incorporates a navigation bar at the top of the }
interface to provide tools for navigation. The following are the modifiers }
}
available to configure this bar. struct CellBook: View {
let book: BookViewModel
navigationTitle(Text)—This modifier defines the title to show in the var body: some View {
navigation bar. There is also a version of this modifier that takes a HStack(alignment: .top) {
Image(book.cover)
Binding property to assign a title dynamically (navigationTitle(Binding)).
.resizable()
.scaledToFit()
navigationBarTitleDisplayMode(TitleDisplayMode)—This .frame(width: 80, height: 100)
modifier determines the display mode for the title. The argument is VStack(alignment: .leading, spacing: 2) {
Text(book.title).bold()
This is the same application created in Chapter 7 to present a list of books, IMPORTANT: Notice that the modifiers are applied to the content.
with the difference that now the list is embedded in a NavigationStack view For instance, the navigationTitle() modifier in our example is applied to
and therefore the interface includes a navigation bar at the top with the the List view, not to the NavigationStack view. This is because the rest of
title provided by the navigationTitle() modifier. the views added to the stack are not going to be embedded in a
The size of the navigation bar created by the NavigationStack view depends NavigationStack view. This view is only required for the initial view to
on the title mode. By default, the mode is defined as automatic and therefore start building the stack. This will become clear later.
all the views in the navigation hierarchy show a large title, but if we scroll
the list down, the size of the navigation bar and the title is automatically 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
reduced to make room for the content.
when the list is implemented with a style that doesn't include any
backgrounds or padding, as in the following example.
Figure 8-2: Navigation stack
Listing 8-2: Setting the title mode

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
Now the title is shown in a standard size and with a predefined style, and Besides the title, the navigation bar can contain other elements, including
all the views in the hierarchy will inherit this mode. images and buttons. SwiftUI provides the following modifiers to add items
and configure the navigation bar and other toolbars.
Figure 8-3: Inline mode
toolbar(id: String, content: Closure)—This modifier adds
elements to a toolbar or navigation bar. The id argument is an
optional identifier used by the system to store the toolbar's last
configuration, and the closure assigned to the content argument
defines the views we want to include.
toolbar(Visibility, for: ToolbarPlacement)—This modifier shows
or hides the bars. The first argument is an enumeration with the

values automatic, visible, and hidden, and the for argument is a list of
values representing the bars we want to modify, separated by comma.
To specify the bars, the structure defines the type properties automatic,
bottomBar, navigationBar, tabBar, and windowToolbar.
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.
argument is an optional identifier used by the system to store the The modifier is applied to the views inside the NavigationStack view and
item's configuration. The placement argument is a structure that then the items are defined inside the closure. In this case, we include one
determines the place and the toolbar where the item will be located. ToolbarItem view with the navigationBarTrailing placement to position the item
The structure includes type properties to define standard on the right side of the navigation bar. The item is defined with a Button
configurations for every system. The properties available are automatic, view and an SF Symbol. The result is shown below.
bottomBar, cancellationAction, confirmationAction, destructiveAction, keyboard,
Figure 8-4: Navigation bar button
navigation, navigationBarLeading, navigationBarTrailing, primaryAction,
secondaryAction, principal, and status. The closure assigned to the content
argument defines the view we want to include in the toolbar.
Listing 8-3: Adding a button to the navigation bar Do It Yourself: Update the ContentView view with the code in Listing 8-
 3. Run the application on the iPhone simulator. Press the button in
struct ContentView: View { the navigation bar. You should see a message printed on the console.
@EnvironmentObject var appData: ApplicationData
var body: some View { The ToolbarItem view defines a single item. If we want to define multiple
NavigationStack { items at once, we can use a ToolbarItemGroup view.
List(appData.userData) { book in
CellBook(book: book)
} ToolbarItemGroup(placement: ToolbarItemPlacement,
.navigationTitle(Text("Books"))
.toolbar { content: Closure)—This view defines multiple items for a toolbar.
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
The placement argument is a structure that determines the place and
print("Delete Element") the toolbar where the items will be located. The structure includes
}, label: { Image(systemName: "trash") })
} 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 be able to scroll the list programmatically, we embed the List view in a
to include. ScrollViewReader view (see Chapter 7, Listing 7-9). Using a ToolbarItemGroup
view, we include two buttons at the right side of the navigation bar. The
The button in the previous example prints a message on the console, but of first button gets the identifier of the first book in the userData array and
course we can perform more meaningful tasks. For instance, we can add scrolls the list to the beginning, and the second button gets the identifier of
buttons to the navigation bar to scroll the list of books to the top and the the last book in the array and scrolls the list to the end.
bottom, as shown next.
Figure 8-5: Group of buttons in the navigation bar
Listing 8-4: Adding multiple buttons

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
.navigationTitle(Text("Books")) property tells the system that the buttons are not essential for the
.toolbar(.hidden, for: .navigationBar)
.toolbar { operation of the application and therefore are placed inside a popup
ToolbarItem(placement: .bottomBar) {
HStack {
menu, as shown in the following example.
Button("Show") {
print("Show Values")
}
Listing 8-6: Defining the buttons intent
}.frame(minWidth: 0, maxWidth: .infinity, alignment: .trailing) 
}
struct ContentView: View {
}
@EnvironmentObject var appData: ApplicationData
}
}
} var body: some View {
 NavigationStack {
List(appData.userData) { book in
CellBook(book: book)
Buttons added to the bottom toolbar are displayed at the center. That's the }
.navigationTitle(Text("Books"))
reason why in our example we have defined the button inside an HStack .navigationBarTitleDisplayMode(.inline)
view, so it could be placed on the right and share the bar with other .toolbar {
ToolbarItemGroup(placement: .primaryAction) {
buttons, if necessary. Notice that we have also implemented the other Button(action: {
version of the toolbar() modifier to hide the navigation bar. print("Adding Book...")
}, label: {
Image(systemName: "plus.app")
Figure 8-6: Bottom toolbar })
}
ToolbarItemGroup(placement: .secondaryAction) {
Button(action: {
print("Sorting Books...")
}, label: {
Label("Sort Books", systemImage: "arrow.up.arrow.down")
})
}
 }
.toolbarRole(.editor)
}
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 The toolbar() modifier includes two ToolbarItemGroup items with one button
present them depending on the space available and the device. For each. The purpose of the button in the first group is to add books to the
instance, the structure returned by the primaryAction property usually places list, which is a primary function and therefore we declare it as a primary
the buttons on the right, and the one returned by the secondaryAction 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 this feature, we must declare the identifiers for the toolbar() modifier and
system places both buttons on the right, but includes the second button in the items, as shown next.
a popup menu.
Listing 8-7: Customizing the bar
Figure 8-7: Primary and secondary buttons 
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData

In this example, we declare the bar with the "mybar" identifier and two
buttons with the identifiers "sort" and "settings". The system uses these
There is a built-in tool that allows the user to add or remove buttons to the identifiers to store the last configuration and restore it the next time the
navigation bar when they are declared as secondary actions. To activate 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. The Menu view works with any button, but it is usually implemented with
toolbar buttons, as illustrated in the following example.
Figure 8-9: Button to customize the toolbar
Listing 8-8: Creating a popup menu

struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
 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
If we want to manually create a popup menu, instead of a button we need represented by a button with an SF Symbol, and the menu opens when the
to add a Menu view to the toolbar. The following is the view's initializer. 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.
following are the changes we need to introduce to the ApplicationData class BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
selected: false)),
in our model to filter books. BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
"book12", year: 2007, selected: false))
]
Listing 8-9: Filtering the data in the model filterValues(search: "")
 }
}
class ApplicationData: ObservableObject { 
var userData: [BookViewModel] {
didSet {
filterValues(search: "") 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
@Published var filteredItems: [BookViewModel] = [] 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
func filterValues(search: String) {
if search.isEmpty {
the search performed by the user, we define a method we will call every
filteredItems = userData.sorted(by: { $0.title < $1.title }) time the list needs an update (when a new search is performed or books
} else {
let list = userData.filter( { item in
are added or removed).
return item.title.localizedCaseInsensitiveContains(search) 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
filteredItems = list.sorted(by: { $0.title < $1.title })
} 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
init() {
userData = [ property with the filter() method. This method receives a value and must
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1", return true or false to determine if it is going to be included in the result. In
year: 2011, selected: false)),
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover: this case, we return true only when the book's title contains the value
"book2", year: 2017, selected: false)), inserted by the user. The resulting array is assigned to the filteredItems
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)), property to make it available to the views. Notice that in both cases, the
BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W. arrays are sorted with the sorted() method, so the values are displayed in
Kernighan", cover: "book4", year: 1988, selected: false)),
BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover: alphabetical order.
"book5", year: 1996, selected: false)), After the testing data is stored in the userData property, we call the
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)), filterValues() method with an empty array to initialize the list. Now we can
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover: use the filteredItems property to show the books, as we do next.
"book7", year: 1996, selected: false)),
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer",
cover: "book8", year: 1982, selected: false)), Listing 8-10: Displaying a search bar
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: struct ContentView: View {
1983, selected: false)), @EnvironmentObject var appData: ApplicationData
@State private var searchTerm: String = ""
the bar's display mode. The structure used to define this value includes the struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
navigationBarDrawer() method for this purpose. If we specify this method with @State private var searchTerm: String = ""
the value always, as shown below, the bar is permanently displayed on the
screen. var body: some View {
NavigationStack {
List(appData.filteredItems) { book in
Listing 8-11: Keeping the search bar on the screen CellBook(book: book)
}
 .navigationTitle(Text("Books"))
struct ContentView: View { }
@EnvironmentObject var appData: ApplicationData .searchable(text: $searchTerm, prompt: Text("Insert title"))
@State private var searchTerm: String = "" .onSubmit(of: .search) { performSearch() }
.onChange(of: searchTerm) { value in
if value.isEmpty {
var body: some View { performSearch()
NavigationStack { }
List(appData.filteredItems) { book in }
CellBook(book: book) }
} func performSearch() {
.navigationTitle(Text("Books")) let search = searchTerm.trimmingCharacters(in: .whitespaces)
} appData.filterValues(search: search)
.searchable(text: $searchTerm, placement: .navigationBarDrawer(displayMode: .always), }
prompt: Text("Insert title")) }
.onChange(of: searchTerm) { value in

let search = value.trimmingCharacters(in: .whitespaces)
appData.filterValues(search: search)
} Because we need to perform the search from two different modifiers, we
}
} move the process to a new method called performSearch(). When the
 Return/Done key is pressed, the closure assigned to the onSubmit() modifier
is executed and we call that method to trim the text and filter the books.
The searchable() modifier automatically stores every character typed by the The performSearch() method is also called from the onChange() modifier, but
user into the Binding property. This means that the search is performed for only when the search is empty. This is because we don't want to update
each character typed or removed from the bar. If we want the user to the list every time the user types or removes a character, as we did before,
decide when the search takes place, we can include the onSubmit() modifier. but still need to do it when the user cancels the search. If the user presses
We have implemented this modifier before with TextField views to the Cancel button, the system assigns an empty string to the searchTerm
processing a value when the Return/Done key is pressed on the keyboard, property, so we check this condition and update the list accordingly.
but if we declare it with the search value, we can use it to perform a search, Another important feature is the possibility of showing a list of values to
as shown below. suggest to the user what to type. SwiftUI includes the following modifier to
create this list.
Listing 8-12: Performing the search when the Return/Done key is pressed

searchSuggestions(Closure)—This modifier creates a list of values }
})
to suggest to the user. The argument provides the list of Text views we .onChange(of: searchTerm) { value in
let search = value.trimmingCharacters(in: .whitespaces)
need to present the values. appData.filterValues(search: search)
}
}
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 In this example, we applied the searchable() modifier, as before, but also
values. To allow selection, the framework includes the following modifier. included the searchSuggestions() modifier to display a list of suggested values.
The closure assigned to this modifier creates a list of Text views with the
searchCompletion(String)—This modifier associates a search value values of the filteredItems property, so only the books that match the current
with a view. The argument is the value we want the search bar to search are included. To help the user identify the books, the Text view
shows the book's title and author, but when the user selects a suggestion,
contain when the view is selected.
only the title is assigned to the search bar by the searchCompletion() modifier.
The Text views with the suggestions may be declared manually to create a
Figure 8-13: Suggestions
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.
through the environment, so the system can determine where to show the
The SearchableView view implements a ForEach view to generate the list of
search bar and when to display it. This means that the environment values
books, so we are able to include an additional row at the top with a button
are only available within the views in the hierarchy. Therefore, to be able to
to dismiss the search. But to avoid interfering with the content, the button
read them, we must embed the list of books in a custom view. In the
is only shown when the user is performing a search. For this purpose, we
following example, we call this view SearchableView.
check whether a search is taking place with the isSearching value and cancel
the search with the dismissSearch value when the Dismiss button is pressed.
Listing 8-14: Cancelling the search programmatically
Notice that the dismissSearch value is a structure that exposes a closure that

we can call as we do with any other closure or function. The result is
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData shown below.
@State private var searchTerm: String = ""
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 Another feature we can incorporate to a search bar are search tokens.
searchScopes() modifier includes two Text views to create two scope buttons, These are values that appear inside the bar to help the user perform
complex search queries. For instance, if we want to allow the user to }, label: {
Image(systemName: "pencil.circle")
search books by author, we can create a token that shows the current })
selected author so the user knows the current search constraint and can }
}
also remove it. .searchable(text: $searchTerm, tokens: $searchTokens, token: { token in
As with scopes, we need a Binding property to store the current tokens and Text(token.name)
})
also provide a Text view to present the tokens on the screen, but because .onChange(of: searchTerm) { _ in performSearch() }
tokens must be created from values that conform to the Identifiable protocol, .onChange(of: searchTokens) { _ in performSearch() }
}
we need to define a custom data type, as shown next. (The data type must func performSearch() {
let search = searchTerm.trimmingCharacters(in: .whitespaces)
also conform to the Equatable protocol to be able to track changes with the appData.filterValues(search: search, author: searchTokens.first?.name ?? "")
onChange() modifier. See Chapter 3 for more information on these }
}
protocols). 
Listing 8-17: Adding tokens In this example, we define a structure called Tokens to create the tokens.
 The structure includes the required id property to identify the value and a
import SwiftUI
String property to store the token's name. The searchable() modifier stores the
List(appData.userData) { book in
CellBook(book: book)
}.navigationTitle(Text("Books")) Our SettingsView view includes two @State properties to store the values of
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
the two Toggle views we use to create a form. Other than that, the view is
NavigationLink(destination: SettingsView(), label: { the same as any other we have created before, but because it represents
Image(systemName: "gearshape")
}) the interface for the whole screen, we should put it in a separate SwiftUI
} file (see Figure 5-108).
}
} The SettingsView view is opened from the ContentView view and therefore it is
}
}
part of its hierarchy, so we don't have to embed it in a NavigationStack view,
 but we still need to declare its title with the navigationTitle() modifier, as we
did for the initial view.
Like the Button view, the NavigationLink view creates a button but with the When the user taps the button in the ContentView view, an instance of the
purpose of replacing the current view with another one. In this case, we SettingsView view is created and presented on the screen transitioning from
create the button with an SF Symbol image of a gear, and set the
right to left.
destination view to be a custom view we call SettingsView.
Figure 8-17: Interface with two views
Listing 8-20: Defining a second view

import SwiftUI
As always, it is recommended to use the name of the environment @EnvironmentObject var appData: ApplicationData
value to define the property. In this example, we call it dismiss. To add a var body: some View {
custom back button, we first remove the one provided by the system with NavigationStack {
List(appData.userData) { book in
the navigationBarBackButtonHidden() modifier, and then add a new button on NavigationLink(destination: {
the left side of the navigation bar (leading) with the toolbar() modifier. When DetailView(book: book)
}, label: {
the button is pressed, we execute the closure provided by the DismissAction CellBook(book: book)
})
structure in the dismiss property, and the view is removed. }.navigationTitle(Text("Books"))
}
}
Figure 8-18: Custom back button }

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
Do It Yourself: Update the SettingsView view with the code in Listing 8- values on the screen.
21. Select the ContentView.swift file to open the view on the canvas.
Listing 8-23: Defining a Detail view
Press the button to open the SettingsView view and then press the Go

Back button to close this view and show the list of books on the
import SwiftUI
screen again.
struct DetailView: View {
let book: BookViewModel
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 var body: some View {
views. A common practice is to declare the rows of a list as the labels of a VStack {
NavigationLink view, so when the user taps on a row, another view opens to Text(book.title)
show additional information. .font(.title)
Text(book.author)
of different data types, SwiftUI provides the following structure to manage user. The following example applies these changes to the ContentView view
them. of our project.
NavigationPath()—This initializer creates a structure to store values Listing 8-24: Defining custom navigation
associated with views in a navigation path. This initializer defines an 
empty path, but there are two more that create instances with struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
predefined paths. We can create it from a collection of values or from @State private var path = NavigationPath()
a CodableRepresentation structure, which allows us to restore previous
var body: some View {
navigation paths when the app is launched. NavigationStack(path: $path) {
List(appData.userData) { book in
NavigationLink(value: book, label: {
The NavigationPath structure includes the following properties and CellBook(book: book)
methods to manage the values. })
}.navigationTitle(Text("Books"))
.navigationDestination(for: BookViewModel.self, destination: { book in
DetailView(path: $path, book: book)
isEmpty—This property returns a Boolean value that indicates if the })
path is empty. }
}
count—This property returns the number of values (views) in the }

path.
append(Value)—This method adds a value (a view) to the path. As we already mentioned, the NavigationLink view and the
removeLast(Int)—This method removes the last value from the modifier are connected by the data type of the value
navigationDestination()
path. The argument is the number of values (views) to remove. By that identifies the link. In our example, we use the BookViewModel structure.
default, only the last value is removed. When the user taps on a NavigationLink view identified with an instance of
the BookViewModel structure, the navigationDestination() modifier associated to
this data type creates a DetailView view with that value, and the view is
When we add a value to the NavigationPath structure, the view
shown on the screen. Once the view is presented, the NavigationStack view
associated to that value is shown on the screen, and when we remove a
appends the NavigationLink value to the NavigationPath structure assigned to
value, the view is removed from the navigation path (and the screen if it is
the stack property. Now, the NavigationPath structure contains a value that
currently active). For this process to work, we need to define a Binding
represents the DetailView view and we can remove it to remove the view
property with a NavigationPath structure (this could be a @State property
from the navigation path, as we do next.
inside the view or a @Published property in the model), initialize the
NavigationStack view with a reference to this property, create the
Listing 8-25: Adding and removing views from the path
NavigationLink views with a value, and implement the navigationDestination()

modifier to tell the system which view to open when a link is tapped by the
struct DetailView: View { which in our case is the BookViewModel structure that represents the
@Binding var path: NavigationPath
let book: BookViewModel DetailView view, so this view is removed from the screen when the button is
pressed.
var body: some View { To add views to the path, we can include another NavigationLink view, as
VStack {
Text(book.title) we did in the example of Listing 8-25. The label was declared as the Image
.font(.title) view that shows the books cover, so when the user taps on the cover, a
Text(book.author)
new view is presented on the screen. Notice that the value for the link was
NavigationLink(value: "Picture View", label: { declared as a string, so the navigationDestination() modifier works with String
Image(book.cover) values this time (String.self). When the user taps on the book's cover, the
.resizable()
.scaledToFit() modifier opens the PictureView view, the NavigationStack view adds the view to
}) the stack and the string to the NavigationPath structure. Now this structure
}.padding()
.navigationTitle(Text("Book"))
contains two values: the BookViewModel structure that represents the
.navigationBarTitleDisplayMode(.inline) DetailView view and the string "Picture View" that represents the PictureView
.navigationBarBackButtonHidden(true)
.toolbar {
view. Again, we pass the reference of the stack property to this view to be
ToolbarItem(placement: .navigationBarLeading) { able to add or remove views from the path.
Button("Go Back") {
path.removeLast()
} Listing 8-26: Going back to any view in the path
}
} 
.navigationDestination(for: String.self, destination: { _ in import SwiftUI
PictureView(path: $path, book: book)
})
struct PictureView: View {
}
@Binding var path: NavigationPath
}
let book: BookViewModel
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
NavigationStack { var body: some View {
DetailView(path: .constant(NavigationPath()), book: ApplicationData().userData[0]) VStack {
} Image(book.cover)
} .resizable()
} .scaledToFit()
 Spacer()
}
.navigationTitle(Text("Cover"))
The DetailView view receives a reference to the stack property, so we can .navigationBarTitleDisplayMode(.inline)
add or remove values from the NavigationPath structure and in consequence .navigationBarBackButtonHidden()
.toolbar {
add or remove views from the navigation path. For instance, in this ToolbarItem(placement: .navigationBarLeading) {
example, we add a button to the navigation bar that calls the removeLast() Button("Go Back") {
path.removeLast()
method. This method removes the last value in the NavigationPath structure, }
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.
Sheets (false), the onDismiss argument is a closure that is executed when the
 sheet is removed, and the content argument is a closure that defines
the view to be shown on the screen.
Sheets are general-purpose modal views. On iPhones, they occupy the fullScreenCover(item: Binding, onDismiss: Closure, content:
whole screen, and on iPads and Mac computers they are shown as a Closure)—This modifier displays a full-screen sheet. The item
rectangular view at the center of the screen. The View protocol defines the argument is an optional Binding value that determines whether the
following modifiers to present a sheet. sheet has to be presented or removed (nil), the onDismiss argument is
a closure that is executed when the sheet is removed, and the
sheet(isPresented: Binding, onDismiss: Closure?, content: content argument is a closure that defines the view to be shown on
Closure)—This modifier displays a sheet. The isPresented argument the screen. The closure receives the value stored in the Binding
is a Binding property of type Bool that determines whether the sheet property.
has to be presented (true) or removed (false), the onDismiss argument
is a closure that is executed when the sheet is removed, and the These modifiers work like the NavigationLink view. We provide a Binding
content argument is a closure that defines the view to be shown on property and the sheet is shown on the screen or removed depending on
the screen. the value of this property. When there is a value or the value is true, the
modifier opens a view on top of the current interface, and we can use this
sheet(item: Binding, onDismiss: Closure?, content: Closure)— view for anything we want. A common practice is opening sheets to allow
This modifier displays a sheet. The item argument is an optional the user to add values to the model. For instance, we can open a sheet to
Binding value that determines whether the sheet has to be presented allow the user to add a book to the list.
or removed (nil), the onDismiss argument is a closure that is executed
when the sheet is removed, and the content argument is a closure Listing 8-28: Displaying a sheet
that defines the view to be shown on the screen. The closure receives 
the value stored in the Binding property. struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@State private var showSheet: Bool = false
These modifiers create a sheet that partially covers the interface. SwiftUI
also includes the following modifiers to create a full-screen sheet. var body: some View {
NavigationStack {
List(appData.userData) { book in
fullScreenCover(isPresented: Binding, onDismiss: Closure, CellBook(book: book)
}.navigationTitle(Text("Books"))
content: Closure)—This modifier displays a full-screen sheet. The .toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
isPresented argument is a Binding property of type Bool that Button(action: {
determines whether the sheet has to be presented (true) or removed showSheet = true
}, label: { Image(systemName: "plus") })
} .textFieldStyle(.roundedBorder)
} TextField("Insert Author", text: $authorInput)
.sheet(isPresented: $showSheet) { .textFieldStyle(.roundedBorder)
AddBookView() TextField("Insert Year", text: $yearInput)
} .textFieldStyle(.roundedBorder)
} .keyboardType(.numbersAndPunctuation)
} Button("Save") {
} storeBook()
 dismiss()
}.buttonStyle(.borderedProminent)
Spacer()
This view defines a @State property to control the state of the sheet and }.padding()
}
adds a Button view to the navigation bar that assigns the value true to this func storeBook() {
property to open the sheet when pressed. The sheet is managed by the let title = titleInput.trimmingCharacters(in: .whitespaces)
let author = authorInput.trimmingCharacters(in: .whitespaces)
sheet() modifier applied to the List view. The modifier is connected to the if let year = Int(yearInput), !title.isEmpty && !author.isEmpty {
showSheet property, so when the value of this property is true a custom view let newBook = BookViewModel(book: Book(title: title, author: author, cover: "nocover",
year: year, selected: false))
called AddBookView is presented on the screen. This is the view we are using appData.userData.append(newBook)
}
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 }
struct AddBookView_Previews: PreviewProvider {
the model. static var previews: some View {
NavigationStack {
AddBookView().environmentObject(ApplicationData())
Listing 8-29: Defining the view for the sheet }
 }
}
import SwiftUI 
In this example, we have included a button to close the view, but there is a Listing 8-30: Disabling the interactive dismiss feature

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 .sheet(isPresented: $showSheet) {
AddBookView()
tool. .interactiveDismissDisabled(true)
}

interactiveDismissDisabled(Bool)—This modifier enables or
disables the possibility of dragging down the view to close it (true To determine the sheet's height, we have different options available. By
disabled, false enabled). default, the height is set to large, which means that the view will take up as
much space as possible. The value medium sets the height to around half the
There are also modifiers to configure the sheet. We can determine the space available, but we can also specify a custom height. For instance, with
height of the sheet and when multiple heights are available, we can
the height() method, we can declare a height in points, and the fraction()
determine if the indicator provided by the system is going to be shown or
not. method allows us to set a height proportional to that of the screen (values
from 0.0 to 1.0). The following example shows how to set it to medium.
presentationDetents(Set, selection: Binding)—This modifier
Listing 8-31: Assigning a predefined height
determines the sheet's height. The first argument is a set of values

that determine all the different sizes the sheet can take. The values
.sheet(isPresented: $showSheet) {
AddBookView() Listing 8-33: Presenting a sheet for every value on the list
.presentationDetents([.medium])
} 
 struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@State private var showSheet: Bool = false
The sheet now occupies less space on the screen, but we can even reduce @State private var editItem: BookViewModel?
it further with the height() method, as shown next.
var body: some View {
NavigationStack {
Listing 8-32: Assigning a custom height List(appData.userData) { book in
 CellBook(book: book)
.background(.white)
.sheet(isPresented: $showSheet) { .onTapGesture {
AddBookView() editItem = book
.presentationDetents([.height(250)]) }
} }.navigationTitle(Text("Books"))
 .toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
Do It Yourself: Replace the sheet() modifier in the ContentView view with showSheet = true
the modifier you want to try. Press the + button to open the sheet. }, label: { Image(systemName: "plus") })
}
Try to assign multiple values to the first argument of the }
.sheet(isPresented: $showSheet) {
presentationDetents() modifier to see how the sheet adapts to different
AddBookView()
heights. }
.sheet(item: $editItem) { item in
AddBookView(book: item)
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 This view defines a @State property called editItem to store the BookViewModel
that value, and when the value nil is assigned to the property, the sheet is value for the sheet. To allow the user to select a row, we add the
closed. onTapGesture() modifier to the CellBook view. We have implemented this
In the following example, we implement this modifier to allow the user to modifier before to perform a task when the user taps a row (see Listing 7-
edit the values of the books. When a row is selected, we assign a copy of 28). In this case, we assign the row's BookViewModel structure to the editItem
the BookViewModel structure that represents the book to the state property, property. When this value changes, the sheet is opened with a second
so the sheet is opened with this value, as shown next. 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 titleInput = book?.title ?? ""
authorInput = book?.author ?? ""
AddBookView view so the user is able to edit the values. yearInput = book?.year ?? ""
}
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 func storeBook() {
let title = titleInput.trimmingCharacters(in: .whitespaces)
also must contemplate that the view might be opened by the user to insert let author = authorInput.trimmingCharacters(in: .whitespaces)
a new book. The following are the modifications we need to introduce to if let year = Int(yearInput), !title.isEmpty && !author.isEmpty {
if let index = appData.userData.firstIndex(where: { $0.id == book?.id }) {
the view for this purpose. let newBook = BookViewModel(book: Book(title: title, author: author, cover:
appData.userData[index].cover, year: year, selected: false))
appData.userData[index] = newBook
Listing 8-34: Editing a book } else {
let newBook = BookViewModel(book: Book(title: title, author: author, cover:

"nocover", year: year, selected: false))
struct AddBookView: View { appData.userData.append(newBook)
@EnvironmentObject var appData: ApplicationData }
@Environment(\.dismiss) var dismiss }
@State private var titleInput: String = "" }
@State private var authorInput: String = "" }
@State private var yearInput: String = "" 
var book: BookViewModel?
 Button("X") {
dismiss()
struct ContentView: View { }.padding(.trailing, 16)
@State private var showPopover: Bool = false }
Text("Press this button when you need help")
var body: some View { .font(.title)
VStack { .padding()
Button("Show Popover") { }.frame(width: 250, height: 250)
showPopover = true }
} }
.popover(isPresented: $showPopover, arrowEdge: .top) { 
HelpView()
}
Spacer() The view can be removed by dragging it down with the finger on iPhones
}.font(.title)
} and by clicking outside the view on iPads, but we can also do it
}

programmatically with the dismiss value, as in this example.
The popover() modifier in Listing 8-35 has two arguments: the isPresented Figure 8-23: Popover on iPad
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. 
Listing 8-36: Defining the content of the popover Do It Yourself: Create a Multiplatform project. Update the ContentView
 view with the code in Listing 8-35. Create a SwiftUI View file called
import SwiftUI HelpView.swift for the view in Listing 8-36. Run the application on
the iPad simulator and press the Show Popover button. You should
struct HelpView: View {
@Environment(\.dismiss) var dismiss see the popover illustrated in Figure 8-23.
In the example of Listing 8-37, we assign the value false to the openAlert Confirmation Dialog
property to remove the alert view when the Cancel button is pressed, but

this is actually not necessary. After the action is performed, the view is
automatically closed.
Our current view contains only one button, but we can include more. If SwiftUI includes another type of alert views called Confirmation Dialogs
there are two buttons, they are shown side by side, but three or more are (also known as Action Sheets). These views are usually presented when our
displayed on a list, with the Cancel button at the end, as shown next. application needs the user to make a decision and there is more than one
option available. The following is the modifier we need to apply to create
Listing 8-38: Defining an Alert view with multiple buttons this view.

.alert("Error", isPresented: $openAlert, actions: { confirmationDialog(String, isPresented: Binding,
Button("Cancel", role: .cancel, action: {})
Button("Delete", role: .destructive, action: { titleVisibility: Visibility, actions: Closure, message: Closure)—
name = "" This modifier creates a confirmation dialog. The first argument is the
})
Button("Save Anyway", role: .none, action: { text for the title, the isPresented argument is a Binding value of type
print("Save value")
Bool that determines whether the view has to be presented (true) or
})
}, message: { Text("Insert your name") }) removed (false), the titleVisibility argument is an enumeration with

the values automatic, hidden, and visible that determines the visibility of
the title, the actions argument defines the buttons to include in the
This modifier includes three buttons. A button to cancel the action and two
view, and the message argument provides the text for the message.
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.
There is not much difference between confirmation dialogs and alert views
other than the design and location. For instance, in iPhones, a confirmation
Figure 8-25: Alert view with multiple buttons 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 following is the modifier provided by the View protocol to define the
tabs.

tabItem(Closure)—This modifier is used to configure the tab items
(the buttons the user taps to select the views). The argument is a
The BooksView and SettingsView views represent two different screens and
closure with a Label view that provides the title and the icon. 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 content of a TabView view is defined inside the closure. The views are
the code for the BooksView, stored in the BooksView.swift file.
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. Listing 8-41: Defining the BooksView

Listing 8-40: Defining a TabView import SwiftUI

struct ContentView: View { struct BooksView: View {
var body: some View { var body: some View {
Text("Books")
TabView {
.font(.largeTitle)
BooksView() }
.tabItem({ }
Label("Books", systemImage: "book.circle") 
})
SettingsView()
.tabItem({ And for the SettingsView view, we must create a file called SettingsView.swift
Label("Settings", systemImage: "gear")
}) and include the following code.
}
}
} Listing 8-42: Defining the SettingsView
 
import SwiftUI
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 struct SettingsView: View {
var body: some View {
text "Books", and for the SettingsView view we include the SF Symbol called Text("Settings")
.font(.largeTitle)
gear and the text "Settings". }
}

Figure 8-28: Tabs
These are simple views but illustrate how a TabView view works. When the
app is launched, the first view on the list is shown on the screen and then
the user can open the rest from the tabs at the bottom. 
Figure 8-29: Views in a TabView view 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.
Do It Yourself: Create a Multiplatform project. Update the ContentView
view with the code in Listing 8-40. Create two SwiftUI View files Listing 8-43: Displaying a badge in a tab
called BooksView.swift and SettingsView.swift for the views in 
struct ContentView: View {
Listings 8-41 and 8-42, respectively. You should see the screen var body: some View {
illustrated in Figure 8-29. Click on the tabs to switch between the TabView {
BooksView()
views. .tabItem({
Label("Books", systemImage: "book.circle")
})
We can include all the views we want in a TabView view. If there is no room SettingsView()
in the tool bar to display all the tabs, the system adds a tab called More .tabItem({
Label("Settings", systemImage: "gear")
that allows the user to select the tabs that are not visible. })
.badge(12)
}
Figure 8-30: More tabs }
}

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. Every time a new value is assigned to the @State property, the view with
that identifier is shown on the screen. This also means that we can
Figure 8-31: Badge determine the view to show by default by initializing the property with the
view's identifier. In our example, we assign the value 1 to the property, so
the Settings view is shown first.
A TabView view is no different from a NavigationStack view other than the way
 the views are presented to the user, so we can integrate it with a model
and define each screen as we did before. For instance, we can define a
The user can tap the tabs at the bottom of the screen to open a view, but view to show a list of books, and another view to configure the list, as
we can also do it from code. The TabView view can use a Binding property to shown in Figure 8-27. The following example updates previous models to
store a value that determines which view is currently opened. By modifying store the app's configuration and show how tabs can work together.
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 Listing 8-45: Updating the model
TabView initializer and identify each view with the tag() modifier. 
class ApplicationData: ObservableObject {
@Published var userData: [BookViewModel]
Listing 8-44: Selecting the initial view @Published var showPictures: Bool = true
 @Published var showYear: Bool = true
struct ContentView: View {
@State private var selectedView: Int = 1 init() {
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
var body: some View {
year: 2011, selected: false)),
TabView(selection: $selectedView) { BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover:
BooksView() "book2", year: 2017, selected: false)),
.tabItem({ BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
Label("Books", systemImage: "book.circle") year: 1995, selected: false)),
}).tag(0) BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W.
Kernighan", cover: "book4", year: 1988, selected: false)),
SettingsView() BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover:
.tabItem({ "book5", year: 1996, selected: false)),
Label("Settings", systemImage: "gear") BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
}).tag(1) 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: .scaledToFit()
"book9", year: 1993, selected: false)), .frame(width: 80, height: 100)
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year: }
1983, selected: false)), VStack(alignment: .leading, spacing: 2) {
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987, Text(book.title).bold()
selected: false)), Text(book.author)
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover: if appData.showYear {
"book12", year: 2007, selected: false))
Text(book.year).font(.caption)
]
}
}
Spacer()
}
}.padding(.top, 5)

Spacer()
}
This new ApplicationData class includes two additional @Published properties to }
}
store Boolean values that users can change from the Settings tab. The struct BooksView_Previews: PreviewProvider {
static var previews: some View {
values determine whether the list of books should show the books' covers BooksView()
and year of publication, so we must use them in the BooksView view to .environmentObject(ApplicationData())
}
configure the rows, as shown below. }

Toggle("Show Pictures", isOn: $appData.showPictures) Listing 8-46 and the SettingsView.swift file with the code in Listing 8-
Toggle("Show Year", isOn: $appData.showYear) 47. Select the Settings tab and turn off the switches. You should see
} the books on the list without the cover and year of publication, as
}
} illustrated in Figure 8-32, right.
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View { In SwiftUI, a TabView view can present two configurations. The user can
SettingsView()
.environmentObject(ApplicationData())
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.
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 tabViewStyle(TabViewStyle)—This modifier defines the
off, the list only includes the books' titles and authors. appearance of a TabView view. The argument is a structure that
conforms to the TabViewStyle structure. The framework defines the
Figure 8-32: Interface configured by the user structures DefaultTabViewStyle and PageTabViewStyle to provide standard
configurations, which in turn include the type properties automatic and
page to apply them to the view.
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. 
Figure 8-34: iPad app configured with three columns The purpose of the NavigationSplitView view is to allow the user to select an
item in the columns on the left and show the result in the column on the
right. For instance, if we show a list of books on the left, when the user }

taps on a book, the information about it is shown in the column on the
right. But if the app runs in a small screen or window, such as an iPhone in
This example includes a @State property called selectedBook to store the book
portrait orientation, the views are presented one by one, as if they were
selected by the user, another for the NavigationSplitView view to store the
inside a NavigationStack view, as shown in Figure 8-35.
To keep track of the selected items, the NavigationSplitView view works with columns' visibility state, and the view itself with two columns. The column
List selection (see Edit Mode in Chapter 7). We need a @State property to on the left (Sidebar) opens the BooksView view with a list of books, and the
keep track of the item selected on the left column, pass a reference of this column on the right (Detail) opens the DetailView view with information
property to the views, and create the rows on the List view with a about the selected book, or a placeholder view if no book was selected yet.
NavigationLink view and the same type of value we use for the selection, so When a book is selected, the BookViewModel structure that represents the
the selection is updated every time the user selects a row. book is assigned to the selectedBook property, the content of the ContentView
A NavigationSplitView view may contain two or three columns. A two columns view is recreated, and a new DetailView view opens on the right column to
design is very common. The interface is simple. We define the present the book on the screen.
NavigationSplitView view and assign the view we want to show on the left to
To know when a new row has been selected, we pass a reference of the
the sidebar argument and the one we want to show on the right to the
selectedBook property to the BooksView view. Therefore, every time the user
detail argument.
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
Listing 8-49: Defining a two-column split view
BooksView view must include a List view connected to the selectedBook

struct ContentView: View { property and also create the rows with a NavigationLink view identified with
@State private var selectedBook: BookViewModel? a value that matches the value stored in that property, as shown next.
@State private var visibility: NavigationSplitViewVisibility = .automatic
var body: some View { Listing 8-50: Defining the view for the left column
NavigationSplitView(columnVisibility: $visibility, sidebar: { 
BooksView(selectedBook: $selectedBook)
import SwiftUI
}, detail: {
if let book = selectedBook {
DetailView(book: book) struct BooksView: View {
} else { @EnvironmentObject var appData: ApplicationData
PlaceholderView() @Binding var selectedBook: BookViewModel?
}
})
var body: some View {
}
} List(appData.userData, selection: $selectedBook) { book in
struct ContentView_Previews: PreviewProvider {
NavigationLink(value: book, label: {
static var previews: some View {
ContentView().environmentObject(ApplicationData()) Text(book.title)
} })
} DetailView view is similar to the one we have created before for an iPhone
.listStyle(.sidebar)
.navigationTitle("Books") application, but now we must consider that it may be shown on devices
}
}
with very different characteristics, such as an iPhone in portrait orientation
struct BooksView_Previews: PreviewProvider { or an iPad in landscape. For this types of applications, Apple recommends
static var previews: some View {
BooksView(selectedBook: .constant(nil)) adapting the interface according to the size classes (see Chapter 6). For
.environmentObject(ApplicationData()) 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
When a NavigationLink is selected, the value is assigned to the selectedBook simple in SwiftUI. All we need to do is to get the view's horizontal size class
property and the selection is performed on the interface. Notice that we from the environment and display one view or the other, as we do in the
have assigned the sidebar style to the list to reproduce the interface in following example.
Figure 8-34.
No functionality is required for the PlaceholderView view. This view only Listing 8-52: Defining a Multiplatform Detail view
opens on some devices to have something to show before the user selects 
import SwiftUI
a book and replaces it with a DetailView view, so we only need it to display a
message. struct DetailView: View {
@Environment(\.horizontalSizeClass) var horizontal
let book: BookViewModel
Listing 8-51: Defining a placeholder view for the right column
 var body: some View {
import SwiftUI Group {
if horizontal == .regular {
DetailLarge(book: book)
struct PlaceholderView: View { } else {
var body: some View { DetailSmall(book: book)
VStack { }
Text("Select a Book") }.padding()
Spacer() .navigationTitle(Text("Information"))
}.padding(50) }
} }
}
 struct DetailLarge: View {
let book: BookViewModel
The PlaceholderView view is displayed on the right column until the user var body: some View {
selects a book, in which case it is replaced with a DetailView view. The HStack {
VStack {
Image(book.cover) class. When the DetailView is loaded, we check the value of the
.resizable()
.scaledToFit() horizontalSizeClass property in the environment and load the corresponding
.frame(maxWidth: 300)
Spacer()
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.
VStack(alignment: .leading, spacing: 4) {
Text(book.title)
.font(.title) Figure 8-36: Different design for iPhones and iPads
Text(book.author)
Text(book.year)
Spacer()
}.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
Spacer()
}
}
}
struct DetailSmall: View {
let book: BookViewModel
When our app is launched on a device with a large screen, the interface is example, we use the onAppear() modifier for this purpose. On iPhones in
presented in two columns. The column on the left shows the list of books, portrait, the application works as before, but on iPads in landscape, the
and the column on the right shows the PlaceholderView view with a message first row is selected and the book is shown on the screen.
to indicate to the user what to do. This view is replaced by the DetailView
view when a book is selected from the list. Depending on the Do It Yourself: Update the ContentView view with the code in Listing 8-
characteristics of our application, sometimes it might be better to show an 53. Run the application on the iPad simulator in landscape mode.
item by default. For instance, we can show the DetailView view instead of the You should see on the screen the first book found in the userData
PlaceholderView view when there are books available in the model, as shown
array.
next.
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
Listing 8-53: Showing an item by default
view. To control the navigation, we need to add a NavigationPath property, as

shown next.
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@State private var selectedBook: BookViewModel? Listing 8-54: Enabling navigation in the right column
@State private var visibility: NavigationSplitViewVisibility = .automatic

var body: some View { struct ContentView: View {
NavigationSplitView(columnVisibility: $visibility, sidebar: { @State private var selectedBook: BookViewModel?
BooksView(selectedBook: $selectedBook) @State private var path = NavigationPath()
}, detail: { @State private var visibility: NavigationSplitViewVisibility = .automatic
if let book = selectedBook {
DetailView(book: book) var body: some View {
} else { NavigationSplitView(columnVisibility: $visibility, sidebar: {
PlaceholderView() BooksView(selectedBook: $selectedBook)
} }, detail: {
})
.onAppear { NavigationStack(path: $path) {
if let book = appData.userData.first { if let book = selectedBook {
selectedBook = book DetailView(path: $path, book: book)
}
} else {
}
PlaceholderView()
}
} }
}

})
.onChange(of: selectedBook) { _ in
Although we could get a book from the model and show the DetailView view path = NavigationPath()
}
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 }
 let book: BookViewModel
The procedure is the same used before to control the navigation path. We var body: some View {
HStack {
declare the NavigationPath property and then use it to initialize the VStack {
NavigationStack view, but because we are enabling navigation for the right Button(action: {
path.append("Picture View")
column, we need to pass a reference of the path to the DetailView view. }, label: {
Notice that to make sure the navigation path always begins with the Image(book.cover)
.resizable()
DetailView view, we clear the NavigationPath structure when a book is selected .scaledToFit()
(when the value of the selectedBook property changes). .frame(maxWidth: 300)
})
When this application is launched, the DetailView view becomes the initial Spacer()
view of the navigation stack, so the rest of the navigation is managed from }
VStack(alignment: .leading, spacing: 4) {
this view. We can, for instance, allow the user to tap on the cover to Text(book.title)
expand it, as we did in previous examples. .font(.title)
Text(book.author)
Text(book.year)
Listing 8-55: Creating a navigation path for the right column Spacer()
}.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)

Spacer()
import SwiftUI }
}
}
struct DetailView: View {
struct DetailSmall: View {
@Environment(\.horizontalSizeClass) var horizontal
@Binding var path: NavigationPath
@Binding var path: NavigationPath
let book: BookViewModel
let book: BookViewModel
left column, the view will open in the same column. For instance, we can
var body: some View {
embed the BooksView view in a NavigationStack view and add a button to the List(appData.userData, selection: $selectedBook) { book in
navigation bar to allow the user to configure the list of books. NavigationLink(value: book, label: {
Text(book.title)
})
Listing 8-57: Adding navigation to the left column }
.listStyle(.sidebar)
 .navigationTitle("Books")
struct ContentView: View { .toolbar {
@State private var selectedBook: BookViewModel? ToolbarItem(placement: .navigationBarTrailing) {
@State private var path = NavigationPath() NavigationLink(value: "Settings View", label: {
@State private var visibility: NavigationSplitViewVisibility = .automatic Image(systemName: "gear")
})
.isDetailLink(false)
var body: some View { }
NavigationSplitView(columnVisibility: $visibility, sidebar: { }
NavigationStack { .navigationDestination(for: String.self, destination: { _ in
BooksView(selectedBook: $selectedBook) SettingsView()
} })
}, detail: { }
NavigationStack(path: $path) { }
if let book = selectedBook { 
DetailView(path: $path, book: book)
} else {
PlaceholderView() 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
.onChange(of: selectedBook) { _ in
false, the SettingsView view opens in the same column, as shown below.
path = NavigationPath()
}
}
}
Figure 8-37: Link opens the view in the left column

Do It Yourself: Update the ContentView view with the code in Listing 8- Three-Columns Layout
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 A NavigationSplitView view can present up to three columns. So far, we have
application on the iPad simulator in landscape orientation. Press the been using the two-column layout, but we can add one more by including
Settings button in the navigation bar. You should see the SettingsView the content argument in the NavigationSplitView's initializer.
view open in the left column. In a three-column layout, the links in the Sidebar column update the
content in the Content column, and the links in the Content column update
the content in the Detail column. Therefore, we have two columns on the
left to select information and a column on the right to show it. This process
is not automatic, we need to prepare the data in the model to feed the
views. For instance, if we want to show a list of authors in the first column,
and the books that belong to the selected author in the second column, as
we do in the example in Figure 8-34, the model must provide the list of
values in that order.
How we organize and store the data in our model depends on the
characteristics or the application. For our example, we are going to store
the list of books in an array and implement a method that extracts the
names of the authors from it.
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 }) call it after the property is initialized with testing values, and also with a
}
init() { property observer applied to the userData property, so the authors are
userData = [ updated every time a book is added or removed from the model.
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
year: 2011, selected: false)), Now that we have the values for the first column, it is time to define the
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover: three-column layout in the ContentView view.
"book2", year: 2017, selected: false)),
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3",
year: 1995, selected: false)), Listing 8-60: Defining a three-column layout
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: struct ContentView: View {
"book5", year: 1996, selected: false)), @State private var selectedAuthor: String?
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove", @State private var selectedBook: BookViewModel?
cover: "book6", year: 1999, selected: false)), @State private var visibility: NavigationSplitViewVisibility = .automatic
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover:
"book7", year: 1996, selected: false)),
var body: some View {
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer",
NavigationSplitView(columnVisibility: $visibility, sidebar: {
cover: "book8", year: 1982, selected: false)),
AuthorsView(selectedAuthor: $selectedAuthor)
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover:
}, content: {
"book9", year: 1993, selected: false)),
BooksView(selectedBook: $selectedBook, selectedAuthor: selectedAuthor)
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
}, detail: {
1983, selected: false)),
if let book = selectedBook {
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
DetailView(book: book)
selected: false)),
} else {
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
PlaceholderView()
"book12", year: 2007, selected: false))
}
]
})
updateAuthors() }
} }
} 

Because we now have two columns with list of values the user can choose
This new ApplicationData class defines the userData property as a normal from, we need two properties to store the selection. We call them
property to store the books, and includes a new @Published property called selectedAuthor and selectedBook. The view in the first column, called AuthorsView,
listAuthors to store the names of the authors. To get the authors from the presents the list of authors available, so we pass a reference to the
data, we have created a method called updateAuthors(). The method gets the selectedAuthor property to capture the user's selection. On the other hand,
names of the authors with the map() method and assigns it to the listAuthors the view in the second column, called BooksView, presents the list of books
property in alphabetical order. Notice that before adding the name, we that belong to the selected author, so we need to pass a reference to the
check if it already exists to avoid duplicates (!list.contains(name)). selectedBook property to control selection and also the value of the
The updateAuthors() method should be called every time the values in the selectedAuthor property to filter the books by author.
userData property change to keep the views up to date. In our example, we
The AuthorsView view must create a list of authors and allow the user to
select one. The code is similar to previous examples. var listBooks: [BookViewModel] {
let list = appData.userData.filter({ item in
return item.author == selectedAuthor
Listing 8-61: Showing the authors })
return list.sorted(by: { $0.title < $1.title })
 }
import SwiftUI var body: some View {
List(listBooks, selection: $selectedBook) { book in
struct AuthorsView: View { NavigationLink(value: book, label: {
@EnvironmentObject var appData: ApplicationData Text(book.title)
@Binding var selectedAuthor: String? })
}.listStyle(.grouped)
.navigationBarTitleDisplayMode(.inline)
var body: some View {
.navigationTitle(selectedAuthor ?? "Undefined")
List(appData.listAuthors, id: \.self, selection: $selectedAuthor) { author in
}
NavigationLink(value: author, label: {
}
Text(author)
struct BooksView_Previews: PreviewProvider {
})
static var previews: some View {
}
BooksView(selectedBook: .constant(nil), selectedAuthor: nil)
.listStyle(.sidebar)
.environmentObject(ApplicationData())
.navigationTitle("Authors")
}
}
}
}

struct AuthorsView_Previews: PreviewProvider {
static var previews: some View {
AuthorsView(selectedAuthor: .constant(nil)) The listBooks property gets the author's books and returns them in
.environmentObject(ApplicationData())
} 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 BooksView view is also similar to previous examples, but now we only
Do It Yourself: Update the ApplicationData class in the
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 ApplicationData.swift file with the code in Listing 8-59. Update the
use it to feed the List view. ContentView view with the code in Listing 8-60. Create a new SwiftUI
View file called AuthorsView.swift and update the view with the
Listing 8-62: Showing the books code in Listing 8-61. Update the BooksView view with the code in
 Listing 8-62. The PlaceholderView view and the DetailView view used in
struct BooksView: View { this example are the same defined for previous examples (see
@EnvironmentObject var appData: ApplicationData
@Binding var selectedBook: BookViewModel? Listings 8-51 and 8-52). Run the application on the iPad simulator in
let selectedAuthor: String? landscape orientation. You should see the three columns on the
screen, as shown in Figure 8-34. Run the application again on the Configuration
iPhone simulator to see how the columns are presented on that 
device.
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.
This example applies the onChange() modifier to change the value of the @State private var selectedBook: BookViewModel?
@State private var visibility: NavigationSplitViewVisibility = .automatic
visibility property to detailOnly when a book is selected. This closes the
columns on the left and expands the column on the right to take up all the var body: some View {
space available. NavigationSplitView(columnVisibility: $visibility, sidebar: {
AuthorsView(selectedAuthor: $selectedAuthor)
In a three-column design, the Detail column is blurred and displaced to the .navigationSplitViewColumnWidth(200)
right when the columns on the left become visible. The NavigationSplitView }, content: {
BooksView(selectedBook: $selectedBook, selectedAuthor: selectedAuthor)
view implements the following modifiers to configure this behavior and .navigationSplitViewColumnWidth(200)
define the columns' width. }, detail: {
if let book = selectedBook {
DetailView(book: book)
navigationSplitViewStyle(NavigationSplitViewStyle)—This } else {
PlaceholderView()
modifier defines how the Detail column is displayed when other }
columns are present. The argument is a structure that conforms to })
.navigationSplitViewStyle(.prominentDetail)
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?
9.1 Asynchronous and Concurrent Tasks
CHAPTER 9 - CONCURRENCY 
Apple systems can take advantage of the large number of cores in modern
processors to execute multiple pieces of code simultaneously, increasing
the amount of work the application can perform at any given time. For
example, we may have a code that downloads a file from the Internet and
another code that shows the progress on the screen. In cases like this, we
cannot wait for one code to finish to execute the other; we need the two
pieces of code to run simultaneously.
To be able to process code in parallel, the system groups units of code in
tasks. In Swift, tasks can be implemented with asynchronous and
concurrent programming. Asynchronous programming is a programming
pattern in which the code waits for a process to finish before completing
the task. This allows the system to share computing resources among many
processes. While waiting, the system can use the resources to perform
other tasks. On the other hand, concurrent programming implements code
that can take advantage of multiple core processors to execute tasks
simultaneously.
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 Tasks
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
Asynchronous and concurrent code is defined by tasks. The Swift Standard
on the screen. While waiting for the server to respond, the thread is free to
Library includes the Task structure to create and manage these tasks. The
perform other tasks, so the system may use it to execute a different task
following is the structure's initializer.
that updates the progress bar. On the right, the tasks were created as
concurrent tasks and therefore they are executed simultaneously in
different threads.
Task(priority: TaskPriority?, operation: Closure)—This initializer
creates and runs a new task. The priority argument is a structure that
helps the system decide when to execute the task. The structure
includes type properties to defined standard priorities. The currently
available are background, high, low, medium, userInitiated, and utility. The
operation argument is a closure with the statements to be executed
by the task.
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.
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 processes are executed one by one, in sequential order. The task waits
The task() modifier creates the task and adds it to the thread. When the for a process to be over before executing the next. In this case, the whole
view is loaded, the closure assigned to the modifier is executed. In the task is going to take 9 seconds to finish (3 seconds per process).
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 Do It Yourself: Update the ContentView structure with the code in
continues executing the statements, and a message is printed on the Listing 9-2. Run the application on the simulator. You should see a
console. message appear on the console after 9 seconds (3 seconds per
process).
Do It Yourself: Create a Multiplatform project. Update the ContentView
structure with the code in Listing 9-1. Run the application on the The task() modifier is useful when all we need is to run an asynchronous
simulator. You should see a message appear on the console after 3 task after the views are loaded, but most of the time tasks are not
seconds. dependent on the views' life cycle and must be created explicitly with the
Task initializer. For instance, we can reproduce the previous example with
A task can perform multiple asynchronous processes. For instance, in the the onAppear() method and a Task structure.
following example we call the loadImage() method three times to download
three images. Listing 9-3: Defining a task explicitly

Listing 9-2: Running multiple asynchronous processes struct ContentView: View {
 var body: some View {
VStack {
struct ContentView: View { Text("Hello, world!")
var body: some View { .padding()
VStack { }
Text("Hello, world!") .onAppear {
.padding() Task(priority: .background) {
}.task(priority: .background) { let imageName1 = await loadImage(name: "image1")
let imageName1 = await loadImage(name: "image1") let imageName2 = await loadImage(name: "image2")
let imageName2 = await loadImage(name: "image2") let imageName3 = await loadImage(name: "image3")
let imageName3 = await loadImage(name: "image3") print("\(imageName1), \(imageName2), and \(imageName3)")
print("\(imageName1), \(imageName2), and \(imageName3)") }
} }
} }
func loadImage(name: String) async -> String { func loadImage(name: String) async -> String {
try? await Task.sleep(nanoseconds: 3 * 1000000000) try? await Task.sleep(nanoseconds: 3 * 1000000000)
return "Name: \(name)" return "Name: \(name)"
}
}
 This example assigns the previous task to a constant and then creates a
timer to call the cancel() method on the task 2 seconds later. In the loadImage()
This view performs the same three processes as before, but now the task is method, we read the isCancelled property and respond accordingly. If the
defined explicitly, which gives us more control over it. For instance, now we task was cancelled, we return the "Task Cancelled" message, otherwise,
can assign the task to a variable and then call the cancel() method to cancel the name is returned as before. Notice that in this case we are working
it. inside a process executed by the task, so we use the type property instead
The cancel() method cancels the task, but the processes are not of the instance property (we read the isCancelled property from the data
automatically cancelled; we must detect whether the task has been type, not the instance). This property returns true or false depending on the
cancelled with the isCancelled property and stop the process ourselves, as state of the current task. As a result, the task is cancelled before it is
shown next. completed.
Tasks can receive and return values. The Task structure includes the value
Listing 9-4: Cancelling a task property to provide access to the value returned by the task. Of course, we
 also need to wait for the task to complete before reading this value, as
struct ContentView: View { shown next.
var body: some View {
VStack {
Text("Hello, world!") Listing 9-5: Reading a value returned by a task
.padding() 
}
.onAppear { struct ContentView: View {
let myTask = Task(priority: .background) { var body: some View {
let imageName = await loadImage(name: "image1") VStack {
print(imageName) Text("Hello, world!")
} .padding()
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in }
print("The time is up") .onAppear {
myTask.cancel() Task(priority: .background) {
} let imageName = await loadImage(name: "image1")
} print(imageName)
} }
func loadImage(name: String) async -> String { }
try? await Task.sleep(nanoseconds: 3 * 1000000000) }
if !Task.isCancelled { func loadImage(name: String) async -> String {
return "Name: \(name)" let result = Task(priority: .background) { () -> String in
} else { let imageData = await getMetadata()
return "Task Cancelled" return "Name: \(name) Size: \(imageData)"
} }
} let message = await result.value
} return message
 }
func getMetadata() async -> Int {
Concurrency Every time a process is declared with the async let statement the system
creates a new concurrent task that runs in parallel with the rest of the

tasks. In the example of Listing 9-8, we create three concurrent tasks
(imageName1, imageName2, and imageName3). The process is the same as before,
Asynchronous tasks are useful when we want to free resources for the
they call the loadImage() method, the method pauses the task for 3 seconds,
system to perform other tasks, like updating the interface, but when we
and returns a string. But because this time the processes run in parallel,
want to run two tasks simultaneously, we need concurrency. For this
the time they take to complete is around 3 seconds (not 9 seconds, as
purpose, the Swift Standard Library defines the async let statement. To turn
previous examples).
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.
Do It Yourself: Update the ContentView structure with the code in
Listing 9-8. Run the application on the simulator. After a few
Listing 9-8: Defining a concurrent task

seconds, you should see a message on the console with the time
struct ContentView: View { that took for the processes to be over.
var body: some View {
VStack {
Text("Hello, world!")
.padding()
}
.onAppear {
let currentTime = Date()
Task(priority: .background) {
async let imageName1 = loadImage(name: "image1")
async let imageName2 = loadImage(name: "image2")
async let imageName3 = loadImage(name: "image3")
actor ItemData { Do It Yourself: Update the ContentView.swift file with the code in
var counter: Int = 0 Listing 9-9. Run the application on the iPhone simulator and press
the button. You should see the values produced by the
func incrementCount() -> String {
counter += 1 incrementCount() method printed on the console. Stop the application.
return "Value: \(counter)" Declare the actor as a class (replace the actor keyword by the class
}
} keyword). Now the application will produce an error when the
struct ContentView: View {
var item: ItemData = ItemData()
method is called by multiple tasks at the same time.
Listing 9-12: Asking the compiler not to check conformity to the Sendable Main Actor
protocol


struct Product: @unchecked Sendable { As we already mentioned, tasks are assigned to execution threads and then
let name: NSString
} the system distributes these threads among the multiple cores of a
 processor to perform the tasks as fast and smoothly as possible. A thread
can manage multiple tasks, and multiple threads may be created for our
IMPORTANT: The @unchecked attribute is usually implemented to application. Besides the threads initialized to process asynchronous and
wrap an unsafe value before sending it to the Main Actor. We will concurrent tasks, the system always creates a thread, called Main Thread,
learn how to work with the Main Actor next and implement the to start the application and run non-asynchronous code, including the code
attribute in practical situations later. that creates and updates the interface. This means that if we try to modify
the interface from an asynchronous or concurrent task, we may cause a
data race or a serious bug. To avoid these conflicts, the Swift Standard
Library defines the Main Actor. The Main Actor is an actor created by the
system that makes sure that every task that wants to interact with the
main thread or modify the elements of the interface waits for other tasks
to finish.
Swift provides two easy ways to make sure that our code runs on the Main
Actor (the main thread): the @MainActor modifier and the run() method.
With the @MainActor modifier we can mark an entire method to run on the
main thread, while the run() method executes a closure in the main thread.
For instance, in the following example we mark the loadImage() method with
@MainActor to make sure that the code inside the method is executed in the
main thread and we are able to modify the value of a Text view with no
issues.
Asynchronous Sequences
Listing 9-16: Defining an asynchronous sequence


import SwiftUI
Sometimes, information is returned as a sequence of values, but the values
may not be available all at once. In cases like this, we can create an
struct ImageIterator : AsyncIteratorProtocol {
asynchronous sequence. This sequence is like an array, but the values are let imageList: [String]
returned asynchronously, so we must wait for each value to be ready. var current = 0
The Swift Standard Library includes two protocols to create asynchronous
mutating func next() async -> String? {
sequences: the AsyncSequence protocol to define the sequence and the guard current < imageList.count else {
AsyncIteratorProtocol protocol to define the code that iterates through the return nil
}
sequence to return the values. The AsyncSequence protocol requires the data try? await Task.sleep(nanoseconds: 3 * 1000000000)
type to include a typealias with the name Element that represents the data
type returned by the sequence, and the following method. let image = imageList[current]
current += 1
return image
makeAsyncIterator()—This method returns the instance of the }
}
iterator in charge of producing the values. The value returned is an
struct ImageLoader : AsyncSequence {
instance of a data type that conforms to the AsyncIteratorProtocol typealias Element = String
protocol. let imageList: [String]
method is called over and over again until the value returned is nil,
var body: some View {
which indicates the end of the sequence. VStack {
Text("Hello World!")
.padding()
To create an asynchronous sequence, we must define two data types, one }
that conforms to the AsyncSequence to describe the data type of the values .onAppear {
Task(priority: .background) {
returned by the sequence and initialize the iterator, and another that let loader = ImageLoader(imageList: list)
conforms to the AsyncIteratorProtocol protocol to produce the values. In the for await image in loader {
print(image)
following example, we define an asynchronous sequence that processes an }
array of strings one by one and returns a sequence of String values. }
} Task Group
}
}


The code in Listing 9-16 simulates the process of asynchronously A task group is a container for dynamically generated tasks. Once the group
downloading images from the Web. The iterator with the next() method is is created, we can add and manage tasks from code as required by the
defined first. In this method, we read the strings from the list array and application. The Swift Standard Library defines the following global
update a counter to know when we have reached the end (the value of the methods to create a group.
counter is equal or greater than the number of elements in the array).
The asynchronous sequence is defined next by the ImageLoader structure. withTaskGroup(of: Type, returning: Type, body: Closure)—This
The structure includes a typealias called Element to indicate that the method creates a task group. The of argument defines the data type
sequence returns String values, and the makeAsyncIterator() method to returned by the tasks, the returning argument defines the data type
initialize the iterator. returned by the group, and the body argument is the closure where
Everything is ready to read the values in the sequence, so we start a task, the tasks are defined. If no values are returned, the arguments may
create an instance of the ImageLoader sequence, and then iterate through be ignored.
the elements with a for in loop. Notice that the for in loop requires the await
keyword to wait for each element of the sequence. The loop runs until the
withThrowingTaskGroup(of: Type, returning: Type, body:
value returned by the iterator is nil. Closure)—This method creates a task group that can throw errors.
The of argument defines the data type returned by the tasks, the
Do It Yourself: Update the ContentView.swift file with the code in returning argument defines the data type returned by the group, and
Listing 9-16. Run the application on the simulator. You should see the the body argument is the closure where the tasks are defined. If no
values in the list array printed on the console every 3 seconds. values are returned, the arguments may be ignored.
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 Figure 9-5: App Transport Security configured to allow URLs from
options available and we can select it from the list. formasterminds.com


Configuring the App Transport Security system may be necessary or
The App Transport Security Settings option is just a container. To configure not, depending on the type of URLs we want our users to be able to access.
the option we must add subitems. To add a subitem, we must click on the Secure URLs are allowed by default, but if we want to allow our users to
arrow on the left (circled in Figure 9-3), and then press the + button again. access non-secure URLs, we must add the options to the app configuration,
The option to allow the app to open non-secure URLs is called "Allow as shown above. For instance, the following example loads an image from
Arbitrary Loads". the non-secure version of our website.
Figure 9-4: App Transport Security configured to allow non-secure URLs Listing 9-18: Loading an image asynchronously

struct ContentView: View {
let website = URL(string: "http://www.formasterminds.com/images/coveruikit4big.png")
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
include values set by the user to determine how the app should work or
values set by the app to restore previous states. These values are stored in This view defines an @AppStorage property with the "counter" key and the
a system-managed database and therefore continue to exist after the name mycounter, and includes a Stepper and a Text view to let the user select a
application is closed and for as long as the user or the application decides value and show the current value on the screen.
to keep them. SwiftUI includes the following property wrapper to store and An @AppStorage property is used as any other state property before, but
retrieve User Defaults values. 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
@AppStorage(String)—This property wrapper stores or retrieves a from User Defaults, and the latest value stored by the user is shown on the
screen.
value from User Defaults. The argument is a string with the key of the
value we want to access.
Figure 10-1: Interface to store settings
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. Do It Yourself: Create a Multiplatform project. Update the ContentView
To create this application, all we need is to define a property with the view with the code in Listing 10-1. Run the application on the iPhone
@AppStorage property wrapper and then use it as we do with a state
simulator. Press the buttons on the stepper to change the value.
property.
Wait for a few seconds. Stop the execution of the app from the Stop
button in Xcode. Run the application again. The value displayed on
Listing 10-1: Storing and reading values from User Defaults
the screen should be the last one you selected.

struct ContentView: View {
@AppStorage("counter") var mycounter: Double = 0 IMPORTANT: The @AppStorage property wrapper can only store
simple values like strings and numbers, but you can also store other
var body: some View {
HStack { types of values, including custom data types, by converting them
Stepper("", value: $mycounter) into Data structures, as we will see later.
update the interval property with the current interval to be able to calculate
As we already mentioned, we can also store values generated by the the time again when the app is relaunched.
application. For example, we can add a value to User Defaults to check how
long it's been since the last time the app was launched. The process to Figure 10-2: Displaying app settings
read and store the value is the same, but this time we need to store a date.
The @AppStorage property wrapper doesn't take Date structures, but we can
store a TimeInterval value from a reference date, and then create the Date
structure from this value when necessary. 
Listing 10-2: Storing app settings in User Defaults Do It Yourself: Update the ContentView view with the code in Listing
 10-2. Run the application on the iPhone simulator. The first time, you
struct ContentView: View { should see all the date components with the value 0. Stop the app
@AppStorage("interval") var interval = Date.timeIntervalSinceReferenceDate
@State private var message: String = ""
from Xcode and run it again. You should see how long it has been
since the last time the app was launched.
var body: some View {
HStack {
Text("\(message)")
The previous examples were designed for didactic purposes. User Defaults
.lineLimit(nil) is frequently used to store the app's settings, and users are provided a
}.onAppear {
let calendar = Calendar.current
separate view where they can change these values and configure the app
let lastDate = Date(timeIntervalSinceReferenceDate: interval) to their liking. In the following example, we are going to follow this
let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from:
lastDate, to: Date())
approach to allow the user to configure the rows of a List view that
message = "You haven't use this app in \(components.year!) years, \(components.month!) presents a list of books. By modifying the values from the Settings view, the
months, \(components.day!) days, \(components.hour!) hours, \(components.minute!) minutes, \ user can define the corners of the book's cover and hide the cover and the
(components.second!) seconds"
interval = Date.timeIntervalSinceReferenceDate year.
}
}
} Figure 10-3: Settings view

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

The model for this project needs the usual Book and BookViewModel In addition to our usual userData property, we have defined three
structures implemented in Chapter 7, Listing 7-3, and the observable @AppStorage properties. The cornerSize property stores a Double value that
object must include the @Published property to store the books, and also the determines the corner radius of the book's cover, and the showYear and
@AppStorage properties to store the app's settings. showCover properties store Boolean values that indicate whether the year
and the cover should be shown or hidden.
Listing 10-3: Managing the app's settings from the model 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
class ApplicationData: ObservableObject { NavigationStack view.
@Published var userData: [BookViewModel]
@AppStorage("cornerSize") var cornerSize: Double = 0
@AppStorage("showYear") var showYear: Bool = true Listing 10-4: Adapting the interface to the app's settings
@AppStorage("showCover") var showCover: Bool = true 
struct ContentView: View {
init() { @EnvironmentObject var appData: ApplicationData
userData = [
BookViewModel(book: Book(title: "Steve Jobs", author: "Walter Isaacson", cover: "book1",
year: 2011, selected: false)), var body: some View {
BookViewModel(book: Book(title: "HTML5 for Masterminds", author: "J.D Gauchat", cover: NavigationStack {
"book2", year: 2017, selected: false)), List(appData.userData) { book in
BookViewModel(book: Book(title: "The Road Ahead", author: "Bill Gates", cover: "book3", VStack {
year: 1995, selected: false)), HStack(alignment: .top) {
BookViewModel(book: Book(title: "The C Programming Language", author: "Brian W. if appData.showCover {
Kernighan", cover: "book4", year: 1988, selected: false)),
Image(book.cover)
BookViewModel(book: Book(title: "Being Digital", author: "Nicholas Negroponte", cover: .resizable()
"book5", year: 1996, selected: false)), .scaledToFit()
BookViewModel(book: Book(title: "Only the Paranoid Survive", author: "Andrew S. Grove",
cover: "book6", year: 1999, selected: false)), .cornerRadius(appData.cornerSize)
BookViewModel(book: Book(title: "Accidental Empires", author: "Robert X. Cringely", cover: .frame(width: 80, height: 100)
"book7", year: 1996, selected: false)), }
BookViewModel(book: Book(title: "Bobby Fischer Teaches Chess", author: "Bobby Fischer", VStack(alignment: .leading, spacing: 2) {
cover: "book8", year: 1982, selected: false)), Text(book.title).bold()
BookViewModel(book: Book(title: "New Guide to Science", author: "Isaac Asimov", cover: Text(book.author)
"book9", year: 1993, selected: false)), if appData.showYear {
BookViewModel(book: Book(title: "Christine", author: "Stephen King", cover: "book10", year:
Text(book.year).font(.caption)
1983, selected: false)),
}
BookViewModel(book: Book(title: "IT", author: "Stephen King", cover: "book11", year: 1987,
}.padding(.top, 5)
selected: false)),
Spacer()
BookViewModel(book: Book(title: "Ending Aging", author: "Aubrey de Grey", cover:
}.padding([.leading, .trailing], 10)
"book12", year: 2007, selected: false))
.padding([.top, .bottom], 5)
]
}
}
}
}
.navigationBarTitle("Books")

.toolbar { }
ToolbarItem(placement: .navigationBarTrailing) { Section {
NavigationLink("Settings", destination: { List {
SettingsView() Toggle("Show Picture", isOn: $appData.showCover)
}) Toggle("Show Year", isOn: $appData.showYear)
} }
} }
} }
} .navigationBarTitle("Settings")
} }
 }

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 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
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 and showYear properties. When the user modifies any of these values, the
value of the showYear property. values in User Defaults are updated, and the views are redrawn, as shown
The navigation bar of the ContentView view includes a button on the right to in Figure 10-3, above.
open a view called SettingsView. This is the view where we allow the user to
change the configuration. Do It Yourself: Download the book covers from our website and add
them to the Asset Catalog. Create a Swift file called
Listing 10-5: Modifying the app's settings ApplicationData.swift for the model in Chapter 7, Listing 7-3. Update
 the ApplicationData class with the code in Listing 10-3 and the
struct SettingsView: View { ContentView view with the code in Listing 10-4. Create a SwiftUI View
@EnvironmentObject var appData: ApplicationData
file called SettingsView.swift for the view in Listing 10-5. Remember
var body: some View { to inject the ApplicationData object into the environment for the app
Form { and the previews (Chapter 7, Listing 7-4). Run the application on the
Section {
HStack(alignment: .top) { iPhone simulator. You should see the interface in Figure 10-3, left.
Text("Corner Radius")
.padding(.top, 6)
Press the Settings button. In the Settings view, change the corner
VStack { radius and turn the switches off (Figure 10-3, center). Press the Back
Slider(value: $appData.cornerSize, in: 0...30)
Image("nocover") button to see the changes on the interface (Figure 10-3, right).
.resizable()
.scaledToFit()
.cornerRadius(appData.cornerSize)
.frame(width: 80, height: 100)
}
}
10.2 Files intermediate directories will also be created or not, and the attributes
argument is a dictionary with values that determine the directory’s
 arguments (e.g., ownership). The value nil sets the arguments by
default, which are usually enough.
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 createFile(atPath: String, contents: Data?, attributes:
manage files and directories, the Foundation framework defines a class Dictionary?)—This method creates a file. The atPath argument
called FileManager. One object of this class is assigned to the app and from specifies the location of the file, including its name and extension, the
that instance we can create, delete, copy, and move files and directories in contents argument represents the content of the file, and the
the storage space reserved for our application. The class offers the attributes argument is a dictionary with values that determine the
following type property to get a reference to this object. file’s arguments (e.g., ownership). The value nil sets the arguments by
default, which are usually enough.
default—This type property returns a reference to the app's
contents(atPath: String)—This method returns the contents of the
FileManager object.
file at the path specified by the atPath argument. The value returned
is an optional of type Data.
The FileManager class offers multiple properties and methods to manage
files and directories. The following are the most frequently used. contentsOfDirectory(atPath: String)—This method returns an
array with the paths of the files and directories inside the directory
urls(for: SearchPathDirectory, in: SearchPathDomainMask)— indicated by the atPath argument.
This method returns an array with the locations of a directory. The for copyItem(atPath: String, toPath: String)—This method copies
argument is an enumeration with values that represent common the file or directory at the path specified by the at argument to the
system directories. The most frequently used are documentDirectory to path specified by the to argument.
represent the Documents directory, and applicationSupportDirectory to
moveItem(atPath: String, toPath: String)—This method moves
reference the Application Support directory. And the in argument is
the file or directory at the path specified by the at argument to the
an enumeration with values that determine the domain in which the
path specified by the to argument.
files are located. The most frequently used is userDomainMask to
represent the user's home directory.
removeItem(atPath: String)—This method removes the file or
directory at the path indicated by the atPath argument.
createDirectory(at: URL, withIntermediateDirectories: Bool,
attributes: Dictionary?)—This method creates a new directory. The fileExists(atPath: String)—This method returns a Boolean value
that determines if the file or the directory at the path specified by the
at argument specifies the location of the directory, including its name,
atPath argument exists.
the withIntermediateDirectories argument indicates whether
init() { The saveFile() method is the same as before, but after creating a new file we
manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask) now check if the name of the file already exists in the listOfFiles property
docURL = documents.first! and only add an instance of the File structure to the array if there isn't
already a file with that name.
if let list = try? manager.contentsOfDirectory(atPath: docURL.path) {
for name in list { After the ApplicationData structure is initialized, the listOfFiles property
let newFile = File(name: name) contains the names of all the files in the directory, so we can show them to
listOfFiles.append(newFile)
} the user.
}
}
func saveFile(name: String) { Listing 10-10: Listing the files created by the user
let newFileURL = docURL.appendingPathComponent(name) 
let path = newFileURL.path
struct ContentView: View {
manager.createFile(atPath: path, contents: nil, attributes: nil)
@EnvironmentObject var appData: ApplicationData
if !listOfFiles.contains(where: { $0.name == name}) {
@State private var openSheet: Bool = false
listOfFiles.append(File(name: name))
}
} var body: some View {
} NavigationStack {
 VStack {
List {
ForEach(appData.listOfFiles) { file in
The first structure, called File, provides the model. It conforms to the Text(file.name)
Identifiable protocol and includes the corresponding id property to identify }
}.listStyle(.plain)
each value, and a property called name to store the file's name. Next, we }.padding()
define a property to store a reference to the FileManager object assigned to .navigationBarTitle("Files")
.toolbar {
the application and another to store the URL of the Documents directory. ToolbarItem(placement: .navigationBarTrailing) {
In the initializer, we get a reference to the FileManager object with the default Button("Add File") {
openSheet = true
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 .sheet(isPresented: $openSheet) {
directory with the contentsOfDirectory() method. This method returns an array AddFileView()
}
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 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.
@Published var currentDirectory: Int
init() {
listOfFiles = [0: [], 1: []]
currentDirectory = 0
manager = FileManager.default
docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!

for (index, directory) in directories.enumerated() {
Do It Yourself: Update the ApplicationData.swift file with the code in let newDirectoryURL = docURL.appendingPathComponent(directory)
let path = newDirectoryURL.path
Listing 10-9 and the ContentView view with the code in Listing 10-10. do {
try manager.createDirectory(atPath: path, withIntermediateDirectories: false,
Run the application on the iPhone simulator. Press the Add File
attributes: nil)
button to add a file. Insert the name of the file and press the Create } catch {
print("The directory already exists")
button. You should see on the screen the list of all the files you have }
created, as shown in Figure 10-5, right. if let list = try? manager.contentsOfDirectory(atPath: path) {
for name in list {
let newFile = File(name: name)
The same way we create a file, we can create a directory. The method to listOfFiles[index]?.append(newFile)
}
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 func saveFile(name: String) {
do not exist (in case the path includes directories we have not created yet), let newFileURL = docURL.appendingPathComponent("\(directories[0])/\(name)")
and the directory’s arguments. This method throws an error if it cannot let path = newFileURL.path
manager.createFile(atPath: path, contents: nil, attributes: nil)
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 if let exists = listOfFiles[0]?.contains(where: { $0.name == name }) {
called original and archived. The original folder will contain the files if !exists {
let newFile = File(name: name)
created by the user, and the archived folder will contain the files the user listOfFiles[0]?.append(newFile)
decides to archive. The following is the model we need for this application. }
}
}
Listing 10-11: Working with custom directories }
 
This model stores two lists of files, one for the original directory and VStack {
Picker("", selection: $appData.currentDirectory) {
another for the archive directory. For this purpose, we turned the listOfFiles ForEach(0..<appData.directories.count, id: \.self) { index in
property from an array into a dictionary. The items in the dictionary are Text(appData.directories[index])
.tag(index)
identified with an integer and their values are arrays of strings. The item }
with the 0 key is going to contain an array with the names of the files in the }.padding([.leading, .trailing], 8)
original directory, and the item with the 1 key is going to contain an array .pickerStyle(.segmented)
List {
with the names of the files in the archived directory. To know which
ForEach(appData.listOfFiles[appData.currentDirectory] ?? []) { file in
directory the user is currently working on, we define a new @Published
Text(file.name)
property called currentDirectory, and to store the names of the directories, }
we define an array called directories with two strings: "original" and }.listStyle(.plain)
}
"archived". .navigationBarTitle("Files")
In the structure's initializer, we initialize the listOfFiles dictionary with two .toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
items, 0 and 1, and empty arrays. We also assign the value 0 to the Button("Add File") {
currentDirectory property to select the original directory by default, and then openSheet = true
initialize the rest of the properties. Finally, we load the files for each }.disabled(appData.currentDirectory != 0 ? true : false)
}
directory with a for in loop; first the original (0) and then the archived (1). }
The saveFile() method stores the files as always, but the path now includes .sheet(isPresented: $openSheet) {
AddFileView()
the name of the original directory, so the new files are always stored in }
that folder. We also have to contemplate the directory when we check }
}
whether the file already exists or not (listOfFiles[0]?.contains(where: { $0.name == }
name })). 
The view must provide a way for the user to switch between one directory
and another. (Although at this time the application doesn't provide a way The ContentView view in Listing 10-12 includes a Picker view with the values in
to assign files to the archived directory.) For this example, we have decided the directories property ("original" and "archived"). When the user selects
to use a segmented picker with two buttons. 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
Listing 10-12: Listing files from two directories 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
struct ContentView: View { archived directory.
@EnvironmentObject var appData: ApplicationData One thing we must contemplate when working with dictionaries, is that
@State private var openSheet: Bool = false
they return an optional. Therefore, the ForEach view won't work unless we
var body: some View { provide an alternative value. In this case, we use the nil-coalescing
NavigationStack { operator (??) to create the list with an empty array if the value of the
property does not exist in the dictionary (it is different than 0
currentDirectory do {
try manager.removeItem(atPath: fileURL.path)
or 1). listOfFiles[currentDirectory]?.removeAll(where: { $0.name == name} )
Because we want the user to be able to add new files only to the original } catch {
print("File was not removed")
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 }
func moveToArchived(name: String) {
is disabled, as shown below. let origin = docURL.appendingPathComponent("\(directories[0])/\(name)")
let destination = docURL.appendingPathComponent("\(directories[1])/\(name)")
if !manager.fileExists(atPath: destination.path) {
Figure 10-6: Files from two directories do {
try manager.moveItem(atPath: origin.path, toPath: destination.path)
listOfFiles[0]?.removeAll(where: { $0.name == name} )
listOfFiles[1]?.append(File(name: name))
} catch {
print("File was not moved")
}
}
}


The first method is called deleteFile() and it receives the name of the file to
Do It Yourself: Update the ApplicationData class with the code in Listing remove. Because the user can only delete files from the directory in which
10-11 and the ContentView view with the code in Listing 10-12. is currently working, we use the value of the currentDirectory property to
Remove the application from the simulator to erase the files created define the file's URL. With this URL, we call the removeItem() method on the
with the previous example. Run the app again. Create new files. FileManager object to delete the file. If the operation is successful, we also
Press the tab buttons to switch between directories. 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.
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 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
remove them from storage. The FileManager class includes the moveItem() and
archived directory. For this purpose, we define two URLs, one to indicate
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 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])/\
view.
(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
Listing 10-13: Moving and deleting files

two URLs to call the moveItem() method on the FileManager object and move
func deleteFile(name: String) {
it. If the operation is successful, we still must update the information in the
let fileURL = docURL.appendingPathComponent("\(directories[currentDirectory])/\(name)") 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 struct RowFile: View {
@EnvironmentObject var appData: ApplicationData
that are equal to the name of the file we are moving. And second, we add let file: File
the file to the array corresponding to the archived directory, so the name
appears on the right folder. var body: some View {
HStack {
The view now needs to provide the tools for the user to move and remove Text(file.name)
each file. For this example, we have decided to generate the rows with a Spacer()
if appData.currentDirectory == 0 {
custom view and include two buttons per row, one to move the file to the Button(action: {
archived folder and another to delete it. appData.moveToArchived(name: file.name)
}, label: {
Image(systemName: "folder")
Listing 10-14: Adding tools to move and delete files .font(.body)
.foregroundColor(Color.green)
 }).buttonStyle(.plain)
struct ContentView: View { }
@EnvironmentObject var appData: ApplicationData Button(action: {
@State private var openSheet: Bool = false appData.deleteFile(name: file.name)
}, label: {
Image(systemName: "trash")
var body: some View { .font(.body)
NavigationStack {
.foregroundColor(Color.red)
VStack { }).buttonStyle(.plain)
Picker("", selection: $appData.currentDirectory) {
}
ForEach(0..<appData.directories.count, id: \.self) { index in }
Text(appData.directories[index]).tag(index)
}
}

}.pickerStyle(.segmented)
List {
ForEach(appData.listOfFiles[appData.currentDirectory] ?? []) { file in Nothing changes in the view, except that instead of showing the name of
RowFile(file: file)
} the file with a Text view, we now create a RowFile view to include the
}.listStyle(.plain) buttons. The buttons are created with SF Symbols, one that represents a
}
.navigationBarTitle("Files") folder and another that represents a trash can. The button with the image
.toolbar { of a folder calls the moveToArchived() method in the model to move the file to
ToolbarItem(placement: .navigationBarTrailing) {
Button("Add File") { the archived directory, but it is only displayed when the current directory is
openSheet = true original (currentDirectory == 0). On the other hand, the button with the image
}.disabled(appData.currentDirectory != 0 ? true : false)
}
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
.sheet(isPresented: $openSheet) {
AddFileView()
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).
}
File Attributes
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
Do It Yourself: Add the methods in Listing 10-13 to the ApplicationData are creationDate (the date the file was created), modificationDate (last time it
class. Update the ContentView.swift file with the code in Listing 10- was modified), size, and type. The following method shows how to read
14. Run the application on the iPhone simulator. Press the Add File these keys to get the attributes of the file selected by the user.
button to add some files and then tap the folder icon on one of the
rows. The file should be moved to the archived folder. Press the Listing 10-15: Reading the file’s attributes

trash can icon to remove a file.
func getDetails(file: UUID) -> (String, String, String, String) {
var values = ("", "", "", "")
if let file = listOfFiles[currentDirectory]?.first(where: { $0.id == file }) {
let fileURL = docURL.appendingPathComponent("\(directories[currentDirectory])/\(file.name)")
let filePath = fileURL.path
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 }
HStack {
wants to read. From this value, the method gets the name of the file and Text("Size:")
builds the path. Once we get the path to the file, we are able to call the .frame(width: 80, alignment: .trailing)
Text(values.2)
attributesOfItem() method to read its attributes. The method returns the .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
values in a dictionary, but they are of type Any, so we must cast them to the }
HStack {
right types. For instance, the type value must be converted into a Text("Date:")
FileAttributeType structure, the size into an Int, and the creationDate into a Date .frame(width: 80, alignment: .trailing)
Text(values.3)
structure. The FileAttributeType structure determines the resource's type. The .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
structure includes properties to represent different types of resources. The }
Spacer()
most frequently used are typeRegular to represent files and typeDirectory to }.padding()
represent directories. By reading these properties, we can determine if the .navigationBarTitle("Details")
}
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 struct FileDetailsView_Previews: PreviewProvider {
static var previews: some View {
the attributes to a tuple and return it. FileDetailsView(file: UUID())
To show the file's attributes to the user, we have decided to create an .environmentObject(ApplicationData())
}
additional view that opens when the user taps on a file on the list. We call
}
it FileDetailsView. 
Listing 10-16: Showing the file’s attributes 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
struct FileDetailsView: View { values constant, and finally the constant is used to show the attributes to
@EnvironmentObject var appData: ApplicationData the user (values.0 is the name, values.1 is the extension, values.2 is the size, and
let file: UUID
values.3 is the date).
Of course, to open this view we must embed each row in a NavigationLink
var body: some View {
let values = appData.getDetails(file: file) view that creates an instance of the FilesDetailsView structure with the file's
return VStack { id. The following are the modifications we must introduce to the List view
HStack {
Text("Name:") in the ContentView view for this purpose.
.frame(width: 80, alignment: .trailing)
Text(values.0)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) Listing 10-17: Opening the details view
} 
HStack {
Text("Extension:") List {
.frame(width: 80, alignment: .trailing) ForEach(appData.listOfFiles[appData.currentDirectory] ?? []) { file in
Text(values.1) NavigationLink(destination: {
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) FileDetailsView(file: file.id)
} , label: { File Content
RowFile(file: file)
})
}

}.listStyle(.plain)

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,
Notice that we send the value of the id property to the FileDetailsView view to
the information we want to store in files has to be converted into a stream
work with the file's id, not its name. The result is shown below.
of Bytes that can be later turned back into the original values. For this
purpose, the Foundation framework includes the Data structure.
Figure 10-8: Details view with the file's attributes
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.
Do It Yourself: Add the method in Listing 10-15 to the ApplicationData
class. Create a SwiftUI View file called FileDetailsView.swift for the UIImage(named: String)—This initializer creates an object that
view in Listing 10-16. Update the List view in the ContentView view contains the image from the file specified by the named argument.
with the code of Listing 10-17. Run the application on the iPhone The argument is a string with the name of the file or the image set in
simulator. Tap on a file. You should see the attributes of that file on the Asset Catalog.
the screen, as shown in Figure 10-8. UIImage(data: Data, scale: CGFloat)—This initializer creates an
object that contains an image generated from the data provided by
the data argument and with an associated scale specified by the scale
argument. If the last argument is ignored, the image is assigned a
scale of 1.
UIImage(contentsOfFile: String)—This initializer creates an object
that contains the image stored in the file indicated by the
contentsOfFile argument. The argument is a string with a path that
determines the location of the file.
UIImage(cgImage: CGImage, scale: CGFloat, orientation: structure with it. The compressionQuality argument is a value
Orientation)—This initializer creates an object that contains an between 0.0 and 1.0 to determine the level of compression.
image generated from a CGImage object and with a scale and
orientation defined by the arguments. The CGImage class is defined by The process to load an image from file is simple. Once we get the data with
the Core Graphics framework to store a low-level representation of an the contents() method provided by the FileManager class, we convert it into an
image, and the orientation argument is an enumeration with the 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
values up, down, left, right, upMirrored, downMirrored, leftMirrored, and
object, then the object must be converted into a Data structure with the
rightMirrored.
pngData() or jpegData() methods, and finally stored in a file with the createFile()
method of the FileManager object implemented before. This method creates
The UIImage class includes properties and methods to get information about
a file if it doesn't exist or updates its content otherwise, so we can use it to
the image and process it. The following are the most frequently used.
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
size—This property returns a CGSize value with the size of the image.
just replace the old image in the file with the new one. As always, most of
scale—This property returns a CGFloat value with the scale of the the process is performed by the model, as shown next.
image.
imageOrientation—This property returns a value that identifies the Listing 10-18: Loading and saving an image

image's orientation. It is an enumeration called Orientation. The values
import SwiftUI
available are up, down, left, right, upMirrored, downMirrored, leftMirrored, and
rightMirrored. class ApplicationData: ObservableObject {
@Published var imageInFile: UIImage?
cgImage—This property returns the image in the Core Graphic var manager: FileManager
format. It is of type CGImage, a Core Graphic data type. var docURL: URL
let listPictures = ["spot1", "spot2", "spot3"]
To convert an image into data, the UIImage class includes the following init() {
methods. manager = FileManager.default
docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!
pngData()—This method converts the image into raw data in the let fileURL = docURL.appendingPathComponent("imagedata.dat")
PNG format and returns a Data structure with it. let filePath = fileURL.path
if manager.fileExists(atPath: filePath) {
jpegData(compressionQuality: CGFloat)—This method converts if let content = manager.contents(atPath: filePath) {
imageInFile = UIImage(data: content)
the image into raw data in the JPEG format and returns a Data }
}
}
func saveFile(namePicture: String) { Listing 10-19: Showing the image in the file
let image = UIImage(named: namePicture)
if let imageData = image?.pngData() { 
let fileURL = docURL.appendingPathComponent("imagedata.dat") struct ContentView: View {
let filePath = fileURL.path @EnvironmentObject var appData: ApplicationData
if manager.createFile(atPath: filePath, contents: imageData, attributes: nil) { @State private var openSheet: Bool = false
imageInFile = image
}
} var body: some View {
} VStack {
} Image(uiImage: appData.imageInFile ?? UIImage(named: "nopicture")!)
 .resizable()
.scaledToFill()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
This model includes a @Published property called imageInFile to store the .onTapGesture {
openSheet = true
image loaded from file and a method to store a new one. The initializer }
defines the manager and docURL properties, as always, and then it checks if }.edgesIgnoringSafeArea(.all)
.sheet(isPresented: $openSheet) {
there is a file called imagedata.dat (the name and extension could be SelectPictureView()
anything we want). If the file exists, we get its content with the contents() }
}
method of the FileManager object. This method returns a Data structure with }
the data to reconstruct the image, so we create a UIImage object with it and 
assign it to the imageInFile property to update the views.
The model also includes an array with the names of three images: spot1, The Image view in Listing 10-19 is created from the UIImage value stored in
spot2, and spot3. These are the images we are going to allow the user to the imageInFile property (the image stored in the file). Notice that the
select to store in the file. When the user selects one of these images, the UIImage initializer always returns an optional, so we must provide an
saveFile() method in the model is called. In this method, we create a new alternative image in case the file can't be loaded, or it doesn't exist. For
UIImage object with the image selected by the user and then convert it into this purpose, we use the nil-coalescing operator (??) to load the image
a Data structure with the pngData() method. If the method is successful, we nopicture from the Asset Catalog.
use the createFile() method of the FileManager object to create the file or The Image view includes the onTapGesture() modifier to open a sheet when
update it with the new image if already exists. Notice that the Data the image is tapped by the user. In the view opened by the sheet, we are
structure created from the image is assigned to the contents argument to going to show to the user three images to select from. We call this view
provide the content to store in the file. If the file is successfully created or SelectPictureView.
updated, we assign the UIImage object to the imageInFile property to update
the views. Listing 10-20: Displaying a list of images for the user to select
The views are simple. The initial view displays the image stored in the 
imageInFile property (the current image in the file), and it allows the user to struct SelectPictureView: View {
@EnvironmentObject var appData: ApplicationData
open a sheet to select another one. @Environment(\.dismiss) var dismiss
Listing 10-21: Reducing the size of an image IMPORTANT: The UIImage class also defines two asynchronous
 methods to prepare an image: byPreparingForDisplay() and
func saveFile(namePicture: String) { byPreparingThumbnail(ofSize: CGSize). The advantage of using
guard let image = UIImage(named: namePicture) else {
return asynchronous methods is that the system can perform other tasks
} while the image is being processed.
guard let thumbnail = image.preparingThumbnail(of: CGSize(width: 100, height: 100)) else {
return
} Besides the UIImage class, there are other classes and structures available in
if let imageData = thumbnail.pngData() {
let fileURL = docURL.appendingPathComponent("imagedata.dat")
Apple's frameworks that can turn values into a Data structure for storage.
let filePath = fileURL.path For instance, the String structure includes a method that turns a string into
if manager.createFile(atPath: filePath, contents: imageData, attributes: nil) {
imageInFile = thumbnail
data an also an initializer that can get back the string from a Data structure.
}
}
}
String(data: Data, encoding: Encoding)—This initializer creates a
 value with the text in the Data structure provided by the data
String
argument. The encoding argument is a structure that determines the
The process performed by the saveFile() method is the same, but before we
type of encoding used to generate the string. The encoding usually
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.
depends on the language the text was written in. The most frequently each time a new value is assigned to the @Published property. For this
used are the structures returned by the properties utf8 and ascii. purpose, we can use property observers, as we do in the following model.
data(using: Encoding, allowLossyConversion: Bool)—This
Listing 10-22: Storing text in a file
method returns a Data structure containing the string from a String

value. The using argument is a structure that determines the type of
import SwiftUI
encoding used to generate the string. The encoding usually depends
on the language the text was written in. The most frequently used are class ApplicationData: ObservableObject {
@Published var textInFile: String = "" {
the structures returned by the properties utf8 and ascii. The didSet {
allowLossyConversion argument determines the precision of the if let textData = textInFile.data(using: .utf8, allowLossyConversion: true) {
conversion. let fileURL = docURL.appendingPathComponent("textdata.dat")
let filePath = fileURL.path
manager.createFile(atPath: filePath, contents: textData, attributes: nil)
The String structure also includes a convenient method to turn a string into }
}
data and store it in a file, all at once. }
var manager: FileManager
var docURL: URL
write(to: URL, atomically: Bool, encoding: Encoding)—This
method converts a string into a Data structure and stores it in the file init() {
manager = FileManager.default
located at the URL specified by the to argument. The atomically docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!
argument determines if we want the data to be stored in an auxiliary
file first to ensure that the original file is not corrupted let fileURL = docURL.appendingPathComponent("textdata.dat")
let filePath = fileURL.path
(recommended). And the encoding argument is a structure that if manager.fileExists(atPath: filePath) {
if let content = manager.contents(atPath: filePath) {
determines the type of encoding used to generate the string. The if let text = String(data: content, encoding: .utf8) {
encoding usually depends on the language the text was written in. textInFile = text
}
The most frequently used are the structures returned by the }
properties utf8 and ascii. }
}
}
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 As we have seen in Chapter 3, property observers are methods that are
inserted by the user in a TextEditor view. The difference from previous executed when a value is assigned to the property (see Chapter 3, Listing 3-
examples is that these types of applications usually store the text as the 45). In this example, we have implemented the didSet() method to the
user types or deletes a character, which means that we must save the file textInFile property to store the value of the property in a file every time a
new one is assigned to it. In the method, we convert the string into a Data Do It Yourself: Create a Multiplatform project. Create a Swift file
structure with the data() method and then store the data in the file with the called ApplicationData.swift for the model in Listing 10-22. Update
createFile() method of the FileManager object, as we did before for images. the ContentView view with the code in Listing 10-23. Remember to
The model's initializer is also similar. We first look for a file with the name inject the ApplicationData object into the environment for the app and
textdata.dat. If the file exists, we get its content with the contents() method,
the previews (Chapter 7, Listing 7-4). Run the application on the
convert the data into a string with the String(data:) initializer, and assign the
iPhone simulator. Type a text. Stop the application from Xcode and
result to the textInFile property to update the views.
run it again. You should see the same text on the screen.
The view for this example must include the TextEditor view to show the text
currently stored in the file and allow the user to change it.
Bundle argument specifies the name of the file or directory we are looking
 for, and the ofType argument specifies the extension.
IMPORTANT: Notice that if the process of reading the document var body: some Scene {
DocumentGroup(newDocument: TextDocument(), editor: { config in
fails, we must return an error. The Foundation framework defines a ContentView(document: config.$document)
})
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 This App structure creates an app to manage documents. We initialize the
fileReadUnknown. DocumentGroup structure with an instance of our TextDocument structure to tell
the app how to create the documents, and a closure that returns a
Now that the document model is ready, we can create the Scene with the ContentView view to provide a view to edit them. Notice that we take the
DocumentGroup structure. The DocumentGroup structure's initializer requires an Binding property from the FileDocumentConfiguration structure received by the
instance of our document model and a closure with the view we want to closure and pass it to the view, so the view can access the document and
use to edit the documents. This closure receives a FileDocumentConfiguration edit its content. For the view to receive this value, it must contain a Binding
structure with a reference to the document and its properties. The property, as shown next.
structure includes the following properties to return these values.
Listing 10-28: Editing the document
document—This is a Binding property with a reference to the 
document. struct ContentView: View {
@Binding var document: TextDocument
fileURL—This property returns a URL structure with the file's URL.
isEditable—This property returns a Boolean value that indicates var body: some View {
GroupBox("Editor") {
whether the user is allowed to edit the document or not. TextEditor(text: $document.documentText)
.cornerRadius(10)
}.padding()
The DocumentGroup structure creates a Scene to process documents and .navigationBarTitleDisplayMode(.inline)
therefore it replaces the WindowGroup structure used before in the App }
}
structure to define the app's windows and the initial view. The following is struct ContentView_Previews: PreviewProvider {
a possible implementation. static var previews: some View {
ContentView(document: .constant(TextDocument()))
}
Listing 10-27: Creating a Document app }


import SwiftUI
This application is similar to the previous one (see Listing 10-23), but
@main instead of storing the text in a file, we store it in a document. A Document
struct TestApp: App { 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 allow the user to save a document on the device, iCloud, or a remote
recent files, and another where we can select the location where we want location. The isPresented argument is a Binding property of type Bool
to store the document and an option to create a new one, as shown below. that indicates whether the interface is shown or hidden. The
When we press the + button, the app creates a new document and opens document argument is the document model we want to use to create
the ContentView view to allow the user to edit its content. the document. The contentType argument determines the type of
content the document is going to handle. The defaultFilename
Figure 10-14: Custom Document app
argument is the name we want to assign to the document. And finally,
the onCompletion argument is the closure to execute when the
process is over. The closure receives a Result value with the
document's URL and an Error value to report errors.
fileImporter(isPresented: Binding, allowedContentTypes:
[UTType], onCompletion: Closure)—This modifier presents an
 interface to allow the user to open a document. The isPresented
argument is a Binding property of type Bool that indicates whether the
Do It Yourself: Create a Multiplatform project. Create a Swift file interface is shown or hidden. The allowedContentTypes argument
called TextDocument.swift for the structure in Listing 10-26. Update determines the type of document we want to allow the user to open.
the App structure with the code in Listing 10-27 and the ContentView And the onCompletion argument is the closure to execute when the
view with the code in Listing 10-28. Run the application on the process is over. The closure receives a Result value with the
iPhone simulator. Select the Browse tab. You should see the + button document's URL and an Error value to report errors.
to add a document (Figure 10-14, left). Press the button. You should
fileMover(isPresented: Binding, file: URL, onCompletion:
see the document editor (Figure 10-14, right).
Closure)—This modifier presents an interface to allow the user to
move a document. The isPresented argument is a Binding property of
The DocumentGroup structure provides an interface with all the tools the user
type Bool that indicates whether the interface is shown or hidden. The
needs to create, modify, and remove documents, but sometimes all we
file argument is the URL of the document we want to move. And the
need is to allow the user to export or import documents from our own
custom interface. For this purpose, SwiftUI includes the following onCompletion argument is the closure to execute when the process is
modifiers. over. The closure receives a Result value with the document's URL and
an Error value to report errors.
fileExporter(isPresented: Binding, document: FileDocument,
contentType: UTType, defaultFilename: String, These modifiers open an interface when a Boolean state changes. For
onCompletion: Closure)—This modifier presents an interface to instance, the fileExporter() modifier opens an interface to allow the user to
export and share a file when the value true is assigned to the state property. state property and the fileExporter() modifier opens the interface. Notice
The modifier creates a document from a document model, as before, but that the modifier is applied to the NavigationStack view, but this is just to
from our own application, as shown next. keep the code organized. The modifier can be applied to any view we want.
Now, we can insert a text and export it as a document from our application
Listing 10-29: Exporting a document without having to create the Scene with the DocumentGroup structure.

struct ContentView: View { Figure 10-15: Export a document
@State private var document = TextDocument()
@State private var openExport: Bool = false
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.

Listing 10-31: Exporting multiple documents
Do It Yourself: Update the ContentView view with the code in Listing 
10-30. Run the application again on your device. Press the Import import SwiftUI
button and select the document created before. You should see the
@main
content of the document on the screen. struct TestApp: App {
@StateObject var appData = ApplicationData()
These examples illustrate how to import and export documents from var body: some Scene {
external sources (the device or a server), but all we do is to create a WindowGroup {
document from the text inserted by the user and replace that text with the ContentView()
.environmentObject(appData)
content of the document the user decides to import. This is not how .fileExporter(isPresented: $appData.openExporter, document: appData.document,
normal applications work. Usually, the application allows the user to contentType: .plainText, defaultFilename: appData.selectedFile.name, onCompletion: { result
in
generate content, store it in files, and provides the tools to export those print("Document saved")
files to other applications or devices if necessary. For instance, we can })
}
implement the same application we have developed before to create and }
show a list of files (see Figure 10-5), but this time incorporate tools to allow }

the user to edit and export the files.
This modifier works like before, but now the property that determines
Figure 10-17: Application to create and export files 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 let path = docURL.appendingPathComponent(selectedFile.name).path
manager.createFile(atPath: path, contents: data, attributes: nil)
 }
import SwiftUI }
func exportDocument(file: File) {
let content = getDocumentContent(file: file)
struct File: Identifiable { selectedFile = FileContent(name: file.name, content: content)
let id: UUID = UUID() document.documentText = content
var name: String openExporter = true
} }
struct FileContent { func getDocumentContent(file: File) -> String {
var name: String selectedFile.name = file.name
var content: String let path = docURL.appendingPathComponent(file.name).path
} if manager.fileExists(atPath: path) {
class ApplicationData: ObservableObject { if let data = manager.contents(atPath: path) {
@Published var listOfFiles: [File] = [] if let content = String(data: data, encoding: .utf8) {
@Published var selectedFile: FileContent return content
@Published var openExporter: Bool = false }
}
var manager: FileManager }
var docURL: URL return ""
var document: TextDocument }
}

init() {
manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask) We have defined two structures to store the data: one called File to store
docURL = documents.first!
document = TextDocument()
the files and another called FileContent to manage the content of the
selectedFile = FileContent(name: "", content: "") selected file. To know which file was selected by the user, we define
another @Published property called selectedFile. The document is stored in a
if let list = try? manager.contentsOfDirectory(atPath: docURL.path) {
for name in list {
normal property called document. When the model is initialized, we defined
let newFile = File(name: name) all the values as before, assign an instance of the TextDocument structure to
listOfFiles.append(newFile)
}
this property, and initialize the selectedFile property with an empty FileContent
} structure (no name and no content).
}
func saveFile(name: String) {
The views are going to offer buttons to add new files to the list, to save the
let newFileURL = docURL.appendingPathComponent(name) text, and one on each row to export the file as a document, but all the
let path = newFileURL.path work is managed by the methods in the model. We have the saveFile()
manager.createFile(atPath: path, contents: nil, attributes: nil)
if !listOfFiles.contains(where: { $0.name == name}) { method to add a new file to the list, the saveContent() method to save the
listOfFiles.append(File(name: name)) 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
func saveContent() { selected file when the user decides to open or export it.
if let data = selectedFile.content.data(using: .utf8, allowLossyConversion: true) {
The following is the main view. In this view, we show the list of files created 
Implementing these methods, we can generate Data structures for When the view defined in Listing 10-35 appears on the screen, the
processing, or we can just store and retrieve the data from a file. In the onAppear() modifier encodes a single string and stores it in a file called
following example, we encode and decode a string to a Data structure and quotes.dat. As we did in previous examples, we first check if the file exists
use FileManager methods to create and read the file. 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 Another requirement for custom structures is that they implement the
content with the contents() method and decode the data with the initializers and methods defined in a protocol called NSCoding. These
unarchivedObject() method to get back the string. initializers and methods tell the system how to encode and decode the
The unarchivedObject() method can only work with data types that conform to data for storage or distribution. Fortunately, the Swift Standard Library
a protocol called NSSecureCoding. That is the reason why we had to specify defines a protocol called Codable that turns a structure into an encodable
the NSString class as the value of the ofClass argument and convert it at the and decodable data type. All we need to do is to get our structure to
end to a String structure with the as operator. (The NSString class conforms to conform to this protocol and the compiler takes care of adding all the
the NSSecureCoding protocol but the String structure does not.) methods required to encode and decode the values. The following example
recreates a model used in previous examples to store information about a
Do It Yourself: Create a Multiplatform project. Update the ContentView book, but now it implements these tools to store the data on file.
view with the code in Listing 10-35. Run the application on the
iPhone simulator. The first time, the file is created and the string Listing 10-36: Encoding and decoding a custom structure
"Undefined" is shown on the screen. Stop and run the application 
import SwiftUI
again. Now the file created before is loaded and you should see the
quote on the screen.
struct Book: Codable {
var title: String
The methods of the NSKeyedArchiver and NSKeyedUnarchiver classes work with var author: String
var year: Int
Property List values (NSNumber, NSString, NSDate, NSArray, NSDictionary, NSData, var cover: String?
and the equivalents in Swift). In the previous example, we were able to to }
struct BookViewModel: Identifiable {
use an NSString value because we wanted to store a simple string, but if we let id: UUID = UUID()
want to archive our own data types, we must convert them to Property List var book: Book
values. Foundation offers two classes for this purpose, PropertyListEncoder
var title: String {
and PropertyListDecoder, which include the following methods to encode and return book.title.capitalized
decode values. }
var author: String {
return book.author.capitalized
encode(Value)—This method of the PropertyListEncoder class encodes }
var year: String {
a value into a Property List value. return String(book.year)
}
decode(Type, from: Data)—This method of the PropertyListDecoder var cover: UIImage {
class decodes a Property List value into a value of the type specified if let imageName = book.cover {
let manager = FileManager.default
by the first argument. The from argument is a Data structure with the let docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first!
data to be decoded. let imageURL = docURL.appendingPathComponent(imageName)
The InsertBookView view includes three TextField views to let the user insert in the same file, replacing the previous one. This example illustrates how
the book's title, author, and year (the cover is always the same). The view custom data types, like the Book structure, are encoded and stored in files,
stores the values inserted by the user in @State properties. When the user but most of the time we need to work with collections of values. For this
presses the Save button to save the book, we check the values in these purpose, all we have to do is to encode an array of structures instead of a
properties. If they are all valid, we call the storeCover() method in the model single one. The following are the modifications required in our observable
to create a file with the cover, create a new instance of the Book structure object to store multiple books.
with all these values, and call the saveFile() method to store the structure in
the file. Listing 10-39: Storing multiple books

Figure 10-18: Interface to store a custom value in a file class ApplicationData: ObservableObject {
@Published var userData: [BookViewModel] = []
var manager: FileManager
var docURL: URL
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 Listing 10-40: Showing all the values stored in the file
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 struct ContentView: View {
saveModel(). This method prepares the data and stores it in a file called @EnvironmentObject var appData: ApplicationData
userdata.dat. In this example, we store in the file instances of the data @State private var openSheet: Bool = false
model (the Book structure), but the observable object stores an array of
var body: some View {
BookViewModel structures. These are instances of the view model used to NavigationStack {
prepare the values for the view. To convert this array into an array of Book List {
ForEach(appData.userData) { book in
structures, the method extracts the values with the map() method. This RowBook(book: book)
method processes the items in the array and returns the instance of the }
}
Book structure inside each BookViewModel structure, creating the list of Book .navigationBarTitle("Books")
structures we need to store in the file. .toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
In this example, we only let the user add books. The books can't be erased Button(action: {
or modified. This is why there is only one method to edit the information openSheet = true
}, label: {
called addBook(). This method receives an instance of the Book structure with Image(systemName: "plus")
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. .sheet(isPresented: $openSheet) {
InsertBookView()
}
}
}
} Do It Yourself: Update the observable object in your model (the
struct RowBook: View { ApplicationData class) with the code in Listing 10-39 and the
let book: BookViewModel
ContentView.swift file with the code in Listing 10-40. Run the
var body: some View { application on the iPhone simulator. Press the + button to add a
HStack(alignment: .top) {
Image(uiImage: book.cover) book and press the Save button to save it. Stop and run the
.resizable() application again. You should see all the books on the screen.
.scaledToFit()
.frame(width: 80, height: 100)
.cornerRadius(10)
VStack(alignment: .leading, spacing: 2) {
Text(book.title)
.bold()
Text(book.author)
Text(book.year)
.font(.caption)
Spacer()
}.padding(.top, 5)
Spacer()
}.padding(.top, 10)
}
}

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.

JSON }
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 The model contains three main components: Entities, Attributes, and
file is compiled and included in the Core Data system created for our Relationships. Entities are the objects, Attributes are the objects’
application. Xcode offers a template to create this file. properties, and Relationships are the connections between objects. The
first step is to add Entities to the model. Entities are created from the Add
Figure 10-21: Option to create a Core Data model in the iOS panel Entity button at the bottom of the editor (Figure 10-22, number 1). When
we press this button, Xcode creates an entity with the generic name
"Entity".
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. We can change the name of the newly created entity by double-clicking
the item (Figure 10-23, number 1) or editing the field in the Data Model
Figure 10-22: Model editor Inspector panel (Figure 10-23, number 2).
An entity defines the objects that are going to be part of the Object Graph,
so the next step is to declare the type of values those objects are going to
manage. For this purpose, entities include Attributes. To add an attribute,
we must select the entity and press the + button under the Attributes area
(Figure 10-23, number 3) or press the Add Attribute button at the bottom
of the editor (Figure 10-22, number 2). The attribute is added with the
generic name "attribute" and the data type Undefined. Again, we can
change the name of the attribute by double-clicking on it or from the Data
Model Inspector panel (Figure 10-23, number 2). For our example, we call
the entity Books and the first attribute title (Figure 10-24, number 1).

Figure 10-24: New Attributes
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
IMPORTANT: The name of entities must start with an upper-case
chunks of data. Storing large amounts of data in a Persistent Store can
letter and the names of attributes and relationships with a lower- affect the system's performance and slow down essential processes like
case letter. This is mandatory and Xcode will show you an error if the searching for values or migrating the model. One alternative is to store the
names are invalid. 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.
Every attribute must be associated with a data type for the objects to know All we need to do is to store the image as Binary Data and select the option
what kind of values they can manage (Figure 10-24, number 2). Clicking on Allows External Storage, available in the Data Model inspector panel inside
the attribute’s type, we can open a menu to select the right data type. The the Utilities Area, as shown below. After the option is selected, the images
most frequently used are Integer 16, Integer 32, Integer 64, Double, Float, assigned to that attribute are stored in separate files managed by the
String, Boolean, Date, and Binary Data. The Integer 16, 32, or 64 options system.
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 Figure 10-26: Option to store images outside the Persistent Store
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.
Figure 10-27: Multiple Entities Figure 10-28: Relationship for the Books entity


A relationship only needs two values: its name (the name of the property)
Entities are blueprints that we use to define the characteristics of the and the destination (the type of objects it is referencing), but it requires
objects we want to store in the database. For instance, when we want to some parameters to be set. We must tell the model if the relationship is
store a new book in our example, we create a new object based on the going to be optional, define its type (To-One or To-Many), and determine
Books entity. That object will have four properties corresponding to the what should happen to the destination object if the source object is
values of its title, year, cover, and thumbnail. The same happens when we
deleted (the Delete Rule). All these options are available in the Data Model
Inspector panel when the relationship is selected, as shown below. To find the right rule for a relationship, we must think in terms of the
information we are dealing with. Is it right to delete the author if one of its
Figure 10-29: Relationship settings books is deleted? In our case, the answer is simple. An author can have
more than one book, so we cannot delete the author when we delete a
book because there could be other books that are connected to that same
author. Therefore, the Nullify rule set by default is the right one for this
relationship. But this could change when we create the opposite
relationship, connecting the Authors entity to the Books entity. We need
this second relationship to search for books that belong to an author.
Figure 10-30 shows a relationship called books that we have created for the
Authors entity.

Figure 10-30: Relationship for the Authors entity
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.

Deny: If there is at least one object at the destination, the source
IMPORTANT: Relationships must always be bidirectional. If we set a
is not deleted (if there is an Authors object assigned to the Books
object, the book is not deleted). relationship from entity A to entity B, we must set the opposite
Nullify: The connections between objects are removed, but the relationship from entity B to A. Core Data offers another type of
objects at the destination are not deleted (if a Books object is relationship called Fetched Properties to connect entities in only one
deleted, the Authors object associated with that book loses the direction. You can add a Fetched Property from the area below the
connection but is not deleted). Relationships area in the model’s editor.
Cascade: The objects at the destination are deleted when the
source is deleted (the Authors object is deleted if one of its books is The new relationship added in Figure 10-30 is in the Authors entity, so
deleted). every Authors object will have a property called books that we can use to
No Action: The objects at the destination are not deleted or retrieve the Books objects associated to the author. Because one author
modified (the connections are preserved, even when the source can have many books, the setting of this relationship is going to differ from
object does not exist anymore). the previous one. In this case, we must set the Type of the relationship as
To-Many (to many books) and modify the Delete Rule according to how we entities with the names Authors and Books. Create the attributes for
want our application to respond when an author is deleted. If we don't these entities as illustrated in Figures 10-25 and 10-27. Create the
want to keep books that are not connected to an author, we should select relationships for both entities as shown in Figure 10-31. Set the
the Cascade option, so when an author is deleted all his or her books are books relationship as To-Many and keep the rest of the values by
deleted too. But if we don't mind having books with no author, then the default.
option should be kept as Nullify.
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.
Core Data Stack 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.
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
NSPersistentContainer(name: String)—This initializer creates an
relationships set up, we must initialize Core Data. Core Data is created from
object that defines a Core Data stack. The name
NSPersistentContainer
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 argument is a string representing the name of the container. This
of the graph in a database. There is an object that manages the model, an value must match the name of the Core Data model (the file's name,
object that stores the data on file, and an object that intermediates without the extension).
between this Persistent Store and our own code. The scheme is called managedObjectModel—This property sets or returns an
Stack. Figure 10-32 illustrates a common Core Data stack. NSManagedObjectModel object that represents the Core Data model.
persistentStoreCoordinator—This property sets or returns the
Figure 10-32: Core Data stack
NSPersistentStoreCoordinator object that manages all the Persistent Stores
available.
viewContext—This property sets or returns the
object in charge of the stack's context that we
NSManagedObjectContext
use to access and modify the Object Graph.
}

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
The model is initialized and injected into the environment as before. To
create an instance of the NSPersistentContainer class with the name of the
provide direct access to the Core Data context, we get a reference from the
Core Data model (in our example, we called it "books") and configure it to
viewContext property of the NSPersistentContainer object and then assign it to
merge the changes between the context and the Persistent Store (this is
the environment's managedObjectContext property with the environment()
the configuration recommended by Apple). The object creates the stack
modifier. From now on, the views can access the Core Data context from
but does not load the Persistent Stores, we have to do it ourselves with the
the environment to fetch, add, modify or remove objects from the
loadPersistentStores() method. After completion, this method executes a
Persistent Store.
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,
Do It Yourself: Create a Swift file called ApplicationData.swift for the
we should warn the user. For instance, we can modify a state property to
model in Listing 10-47 and update the App structure with the code in
open an Alert View to report the situation. In this example, we just call the
Listing 10-48. Replace the value of the name argument in the
fatalError() function to stop the execution of the app.
NSPersistentContainer initializer with the name of your model's file (in
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 our example, it is called "books"). At this moment, the app doesn't
Persistent Store. The environment offers the managedObjectContext property do anything other than creating the stack. We will see how to read
for this purpose. The following are the modifications we must introduced and store data next.
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 To ask Xcode to create the subclasses for us, we must select the entities
initializer and methods to create and manage the objects. 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
NSManagedObject(context: NSManagedObjectContext)—This in the Name field (Figure 10-33, number 1). Once the options are set, the
initializer creates a new instance of the NSManagedObject class, or a classes are automatically created. For example, when we set these options
subclass of it, and adds it to the context specified by the context for the entities in our model, Xcode creates a subclass of NSManagedObject
argument. called Books with the properties title, year, cover, thumbnail, and author, and a
fetchRequest()—This type method generates a fetch request for an subclass called Authors with the properties name and books. From now on, all
entity. A fetch request is a request we use to fetch objects of a 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.
particular entity from the Persistent Store.
entity()—This type method returns a reference to the entity from Do It Yourself: By default, Xcode configures the Entities and creates
which the managed object was created. It is an object of type the subclasses for us, but you can do it yourself from the editor.
NSEntityDescription with a description of the entity.
Select the Books or the Authors entity, open the Data Model
Inspector panel on the right, and make sure that the value of the
To simplify our work, the system allows us to define subclasses of the
Codegen option is set to Class Definition and the name of the Entity
NSManagedObject class that correspond to the entities in our model. (Instead
is assigned to the Name field, as shown in Figure 10-33.
of creating instances of the NSManagedObject class, we create instances of
the Books and Authors classes.) Because this is common practice, Xcode
IMPORTANT: The subclasses of the NSManagedObject class created to
automatically creates the subclasses for us. All we need to do is to
associate each Entity with a subclass from the Data Model Inspector panel. represent each entity in our model are not visible in Xcode. They are
created internally and automatically modified every time entities or
Figure 10-33: Entity's subclass attributes are added or removed from the model. If you decide not
to use these subclasses, you can select the value Manual/None from Fetch Request
the Codegen option and work directly with NSManagedObject objects or

define your own subclasses.
To store an object in the Persistent Store, we must create an
NSManagedObject object (or an instance of our subclasses), add it to the
context, and then save the context. The process to get the objects back
from the Persistent Store is the opposite. Instead of moving the changes
from the context to the Persistent Store, we must fetch the objects from
the Persistent Store and move them into the context. Once the objects are
in the context, we can read their properties, modify the values or delete
them. Core Data defines the NSFetchRequest class to fetch objects from the
Persistent Store. The class includes the following properties for
configuration.
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
Reading and writing information on a database is a delicate process. The It is time to see how all these tools work together to store and retrieve
database may be accessed by different parts of the application and from data from a Persistent Store. We are going to use a project like the one
different threads, which can cause errors or even data corruption. To make created before for archiving. The purpose of the application is to show the
sure the data is accurate and safe, we must access the database from the list of books stored in the Persistent Store and add new ones.
same thread assigned to the Core Data context. For this purpose, the
NSManagedObjectContext class includes the following asynchronous method. Figure 10-34: Interface to work with Core Data
with it, and return it. On the other hand, if the thumbnail property is empty, if !newTitle.isEmpty && newYear != nil {
Task(priority: .high) {
or there was a problem converting the data into an image, we return a await storeBook(title: newTitle, year: newYear!)
UIImage object with the image nopicture from the Asset Catalog. }
}
Now, it is time to let the user create new Books objects. The ContentView view }
includes a NavigationLink in the navigation bar that opens the InsertBookView }
}
view for this purpose. }
func storeBook(title: String, year: Int32) async {
await dbContext.perform {
Listing 10-50: Adding new objects to the Persistent Store let newBook = Books(context: dbContext)
 newBook.title = title
newBook.year = year
import SwiftUI
newBook.author = nil
import CoreData
newBook.cover = UIImage(named: "bookcover")?.pngData()
newBook.thumbnail = UIImage(named: "bookthumbnail")?.pngData()
struct InsertBookView: View { do {
@Environment(\.managedObjectContext) var dbContext try dbContext.save()
@Environment(\.dismiss) var dismiss dismiss()
@State private var inputTitle: String = "" } catch {
@State private var inputYear: String = "" print("Error saving record")
}
}
var body: some View {
}
VStack(spacing: 12) {
}
HStack {

Text("Title:")
TextField("Insert Title", text: $inputTitle)
.textFieldStyle(.roundedBorder) All the interaction between our code and the Persistent Store is done
}
HStack { through the context. When we want to access the objects already stored,
Text("Year:") add new ones, remove them, or modify any of their values, we have to do
TextField("Insert Year", text: $inputYear)
.textFieldStyle(.roundedBorder) it in the context and then move those changes from the context to the
} Persistent Store. The @FetchRequest property wrapper automatically gets a
HStack {
Text("Author:") reference of the context from the environment (this is the reference we
Text("Undefined") injected into the context in the App structure of Listing 10-48), but when
.foregroundColor(.gray)
}.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
working directly with Core Data, we must get the reference from the
Spacer() environment with the @Environment property wrapper and the
}.padding()
managedObjectContext key. In this example, we called this property dbContext.
.navigationBarTitle("Add Book")
.toolbar { The view includes two TextField views to let the user insert the title of the
ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
book and the year of publication, and a button in the navigation bar to save
let newTitle = inputTitle.trimmingCharacters(in: .whitespaces) the values. When the button is pressed, the code checks if the values are
let newYear = Int32(inputYear)
valid and then calls an asynchronous method to create and store a new learn how to generate the previews and run the application in the
object in the Persistent Store. We moved the code to an asynchronous canvas.) Press the + button and insert a book. You should see the
method so that we can implement the perform() method provided by the interface illustrated in Figure 10-34.
context and thus ensure that the process is performed in a safe thread,
which is recommended every time we are adding, removing, or modifying There are no view models in Core Data. The objects stored in the Persistent
an object. Store (Books and Authors in our example), represent the application's data
The process to add a new book begins with the creation of the new object model. They store the values and return them as they are. But the views
with the Books() initializer. This not only creates a new object of type Books need the values to be formatted or casted before showing them on the
but it also adds it to the context specified by the argument (dbContext). The screen. For instance, in the RowBook view in Listing 10-49, we had to
next step is to assign the values to the object's properties. We assign the process the value of the thumbnail property with a computed property to
value of the first input field to the title property, the value of the second turn it into an UIImage view. We also had to use the nil-coalescing operator
input field to the year property, the images bookcover and bookthumbnail to show a string if there was no value in the title and author properties. And
to the cover and thumbnail properties, respectively (we assign standard we even had to cast the value of the year property to a string with the
images to every book for now), and the nil value to the author property (we String() initializer. All this work should not be done by the view, it should be
still don't have an Authors object to associate with this book). done by a view model. To create a view model for the Core Data objects,
The Books() initializer inserts the new object into the context, but this we can extend the classes defined by the system (see Extensions in Chapter
change is not permanent. If we close the app after the values are assigned 3). For example, we can create an extension of the Books class to provide
to the properties, the object is lost. To persist the changes, we must save computed properties that always return String values for the views to
the context with the save() method. This should be done every time we display, and process the images in the thumbnail and cover properties, so we
finish a process that modifies the context. The method takes the don't have to do it inside the view.
information in the context and updates the Persistent Store with it, so
everything is stored permanently in the file. Listing 10-51: Defining a view model for the Core Data objects

Do It Yourself: This example assumes that you have followed the import SwiftUI
previous steps to create a project, prepare a model with the Books
extension Books {
and Authors entities, define the Core Data stack, and inject the var showTitle: String {
context into the environment in the App structure (Listing 10-48). return title ?? "Undefined"
}
Download the bookcover, bookthumbnail, and nopicture images var showYear: String {
from our website and add them to the Asset Catalog. Update the return String(year)
}
ContentView.swift file with the code in Listing 10-49. Create a var showAuthor: String {
return author?.name ?? "Undefined"
SwiftUI View file called InsertBookView.swift for the view in Listing
}
10-50. Run the application on the iPhone simulator. (Later we will var showCover: UIImage {
var body: some View { Figure 10-35: Interface to list and add authors
HStack(alignment: .top) {
Image(uiImage: book.showThumbnail)
.resizable()
TextField("Insert Year", text: $inputYear)
.textFieldStyle(.roundedBorder)
}
HStack(alignment: .top) {
Text("Author:")
VStack(alignment: .leading, spacing: 8) {
Text(selectedAuthor?.name ?? "Undefined")
.foregroundColor(selectedAuthor != nil ? Color.black : Color.gray)
NavigationLink(destination: AuthorsView(selected: $selectedAuthor), label: {
Text("Select Author")
 })
}
}.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
The view on the left in Figure 10-35 is the InsertBookView view introduced Spacer()
before to insert new books. This view now shows three input options, an }.padding()
.navigationBarTitle("Add Book")
input field to insert the title of the book, another to insert the year, and .toolbar {
the Select Author button to select the author. This button is a NavigationLink ToolbarItem(placement: .navigationBarTrailing) {
Button("Save") {
view that opens a view to list all the authors available (Figure 10-35, let newTitle = inputTitle.trimmingCharacters(in: .whitespaces)
center). In turn, this view includes a + button in the navigation bar to open let newYear = Int32(inputYear)
if !newTitle.isEmpty && newYear != nil {
a view that includes an input field to insert the name of an author (Figure Task(priority: .high) {
10-35, right). The first step we need to take to create this interface is to await storeBook(title: newTitle, year: newYear!)
}
add the Select Author button to the InsertBookView view, as shown below. }
}
}
Listing 10-53: Selecting the author }
 }
func storeBook(title: String, year: Int32) async {
struct InsertBookView: View { await dbContext.perform {
@Environment(\.managedObjectContext) var dbContext let newBook = Books(context: dbContext)
@Environment(\.dismiss) var dismiss newBook.title = title
newBook.year = year
@State private var selectedAuthor: Authors? = nil newBook.author = selectedAuthor
@State private var inputTitle: String = ""
newBook.cover = UIImage(named: "bookcover")?.pngData()
@State private var inputYear: String = "" newBook.thumbnail = UIImage(named: "bookthumbnail")?.pngData()
do {
var body: some View { try dbContext.save()
VStack(spacing: 12) { dismiss()
HStack { } catch {
Text("Title:") print("Error saving record")
TextField("Insert Title", text: $inputTitle) }
.textFieldStyle(.roundedBorder) }
} }
HStack { }
Text("Year:") 
})
}
Every time an author is selected or created, we must get its Authors object }
and send it back to the InsertBookView view to assign it to the book. To store }
}
this value, the view includes a @State property called selectedAuthor. If the struct AuthorsView_Previews: PreviewProvider {
property contains an Authors object, we show the value of its name property static var previews: some View {
AuthorsView(selected: .constant(nil))
to the user, otherwise, we show the text "Undefined". Below the name, we }
include a NavigationLink button to open a view called AuthorsView to let the }

user select an author. This view must list the authors and provide a button
to add more.
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,
Listing 10-54: Listing all the authors available
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
import SwiftUI
onTapGesture() modifier to let the user select an author. When the user taps
struct AuthorsView: View { on the name of an author, we assign the Authors object to a @Binding
@FetchRequest(sortDescriptors: [], predicate: nil, animation: .default) private var property called selected and close the view. Because the selected property is
listOfAuthors: FetchedResults<Authors>
@Environment(\.dismiss) var dismiss
connected to the selectedAuthor property defined in the InsertBookView view,
@Binding var selected: Authors? 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
var body: some View {
List {
looking for is not on the list, the user can press a button in the navigation
ForEach(listOfAuthors) { author in bar that opens the InsertAuthorView view to insert a new author.
HStack {
Text(author.showName) Listing 10-55: Inserting new authors
} 
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment:
.leading) import SwiftUI
.background(.white) import CoreData
.onTapGesture {
selected = author struct InsertAuthorView: View {
dismiss() @Environment(\.managedObjectContext) var dbContext
} @Environment(\.dismiss) var dismiss
} @State private var inputName: String = ""
}
.navigationBarTitle("Authors")
.toolbar { var body: some View {
ToolbarItem(placement: .navigationBarTrailing) { VStack {
NavigationLink(destination: InsertAuthorView(), label: { HStack {
Image(systemName: "plus") Text("Name:")
TextField("Insert Name", text: $inputName)
.textFieldStyle(.roundedBorder) the view with the book’s information and shows the name of the selected
}
HStack { author on the screen (Figure 10-35, left). The Authors object that represents
Spacer() the author is assigned to the book’s author property and therefore the name
Button("Save") {
let newName = inputName.trimmingCharacters(in: .whitespaces) of the author is now shown on the list of books.
if !newName.isEmpty {
Task(priority: .high) {
await storeAuthor(name: newName) Do It Yourself: Update the InsertBookView view with the code in Listing
} 10-53. Create a SwiftUI View file called AuthorsView.swift for the
}
} view in Listing 10-54 and another called InsertAuthorView.swift for
}
Spacer()
the code in Listing 10-55. Run the application on the iPhone
}.padding() simulator. Press the + button to insert a new book. Press the Select
.navigationBarTitle("Add Author")
} Author button. Press the + button to add an author. After adding the
func storeAuthor(name: String) async { author, tap on it to select it. You should see the name of the author
await dbContext.perform {
let newAuthor = Authors(context: dbContext) on the screen.
newAuthor.name = name
do {
try dbContext.save()
dismiss()
} catch {
print("Error saving record")
}
}
}
}

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
Previews })
}
}
 
So far, we have run the application on the iPhone simulator or a device. This model creates the Persistent Store as before, but it also includes a
This is because Apple recommends to create the Persistent Store for type property called preview to create a Persistent Store in memory for the
previews in memory, so all the objects added for testing are removed as previews. The closure assigned to this property initializes the ApplicationData
soon as the preview is over. Therefore, if we want to develop the interface object with the value true. The initializer checks this value and assigns a URL
on the canvas, we need to define two Persistent Stores, one for the structure with a null URL to the url property of the NSPersistentStoreDescription
application and another for the previews. In the following example, we object returned by the persistentStoreDescriptions property of the
achieve this with a type property. The model works as before, but if we NSPersistentContainer class. The NSPersistentStoreDescription object is used by Core
read this property, we get an ApplicationData class that creates a Persistent Data to create and load the Persistent Store, so when we assign a URL with
Store in memory. To know what kind of Persistent Store to create, we add a the "/dev/null" path to this object, the Persistent Store is created in
Boolean parameter to the ApplicationData initializer called preview. If the value memory.
of this parameter is false, the Persistent Store is created as always, Now that we have a type property to create the Persistent Store for
otherwise, it is created in memory. 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
Listing 10-56: Creating a Persistent Store for previews time we access the viewContext property of the NSPersistentContainer object
 returned by the ApplicationData object created by the preview property, as
import SwiftUI shown next.
import CoreData
class ApplicationData: ObservableObject { Listing 10-57: Accessing the Core Data context from the preview
let container: NSPersistentContainer 
struct ContentView_Previews: PreviewProvider {
static var preview: ApplicationData = { static var previews: some View {
let model = ApplicationData(preview: true) ContentView()
return model .environment(\.managedObjectContext, ApplicationData.preview.container.viewContext)
}() }
}
init(preview: Bool = false) { 
container = NSPersistentContainer(name: "books")
if preview {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") Do It Yourself: Update the ApplicationData class with the code in Listing
} 10-56. Update PreviewProvider structure in the ContentView.swift file
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? { with the code in Listing 10-57. Use the same environment() modifier to
fatalError("Unresolved error \(error), \(error.userInfo)") inject the context in the preview of every view we have created so
}
far. Select the ContentView view. You should be able to add new books Sort Descriptors
and see the list of books on the canvas. 
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.
@FetchRequest(sortDescriptors: [SortDescriptor(\Books.title, order: .forward)], predicate: nil, structure returned by the lexical property sorts the values alphabetically. For
animation: .default) var listOfBooks: FetchedResults<Books> instance, if we have an array with the values 1, 2, and 10, the order will be
1, 10, 2 (the value 10 begins with 1, which comes before 2). This also
var body: some View { applies to letters. Uppercase letters come before lowercase letters, so
NavigationStack {
List {
words beginning with a lowercase letter will be moved to the end of the
ForEach(listOfBooks) { book in list. The structure returned by the localizedStandard property sorts the values
RowBook(book: book)
}
numerically (1, 2, 10), which also applies to letters (words beginning with
} uppercase letters will be sorted along with lowercase letters). And finally,
.navigationBarTitle("Books")
.toolbar {
the structure returned by the localized property sorts the values
ToolbarItem(placement: .navigationBarTrailing) { alphabetically as the lexical structure, but uppercase and lowercase letters
NavigationLink(destination: InsertBookView(), label: { are sorted together, as the localizedStandard structure.
Image(systemName: "plus")
}) By default, the values are sorted with the localizedStandard structure. The
} following example shows how to apply a lexical structure instead.
}
}
} Listing 10-60: Sorting with a lexical comparator
}
 
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
NSPredicate(format: String, argumentArray: [Any]?)—This If we want to search for a value in a relationship, we must concatenate the
initializer creates an NSPredicate object with the conditions set by the properties with dot notation, as we did before to read the name property in
format argument. The argumentArray argument is an optional array the Authors object. For example, the following request searches for books
of values that replace placeholders in the string assigned to the written by Stephen King.
format argument. The argumentArray argument may be ignored or
replaced by a list of values separated by commas. Listing 10-63: Filtering books by author

To filter values in a request, we must create the NSPredicate object and @FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "author.name = 'Stephen
assign it to the predicate argument of the @FetchRequest property wrapper. King'")) var listOfBooks: FetchedResults<Books>
The following example assigns an NSPredicate object to the request in the 
ContentView view to search for the books that have the value 1983 assigned
to their year property. Do It Yourself: Update the ContentView view with the code in Listing
10-62 and try the @FetchRequest property defined in Listing 10-63 to
Listing 10-62: Filtering books by year see how predicates work. Add books with different authors and
 years to test the filters.
struct ContentView: View {
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "year = 1983")) var Of course, these are predefined conditions, but we can allow the user to
listOfBooks: FetchedResults<Books> 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
var body: some View {
NavigationStack {
inserted by the user.
List {
To dynamically assign a new predicate to the request, the FetchedResults To add a search bar, we implement the searchable() modifier (see Listing 8-
structure includes the nspredicate property. In the following example, we 10), and to perform the search, we implement the onChange() modifier. This
show how to search books by year by assigning a new predicate to this modifier checks the value of a state property called search. If the value of
property every time the user inserts a 4 digits number in the search field. this property changes, the modifier executes a closure where we replace
the current predicate with a new one. When the value inserted by the user
Listing 10-64: Assigning new predicates to search for values inserted by the is a number, we filter books by year, otherwise, we assign a nil value to the
user nsPredicate property to show all the books available. (Notice that the
 NSPredicate initializer only takes Property List values, so we must cast the
struct ContentView: View { Int32 value into an NSNumber.)
@FetchRequest(sortDescriptors: [], predicate: nil, animation: .default) var listOfBooks:
FetchedResults<Books>
@State private var search: String = "" Figure 10-37: Searching
Predicates use comparison and logical operators like those offered by Swift.
For example, we can compare values with the operators = (==), !=, >, <, >= Listing 10-65: Filtering values with predicate keywords
and <=, and also concatenate conditions with the characters && (or the 
word AND), || (or the word OR) and ! (or the word NOT). Predicates also .searchable(text: $search, prompt: Text("Insert Name"))
.onChange(of: search) { value in
include keywords for a more meticulous search. The following are the most
if !value.isEmpty {
frequently used. listOfBooks.nsPredicate = NSPredicate(format: "author.name BEGINSWITH[c] %@",
value)
} else {
BEGINSWITH—The condition determined by this keyword is true listOfBooks.nsPredicate = nil
}
when the expression on the left begins with the expression on the }
right. 
The view includes two TextField views for the user to be able to modify the Deleting Objects
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
Deleting objects in the Persistent Store is no different than any other
properties and the context is saved.
values in the model. As we did in the example in Listing 7-22, we must
In this case, we do not create a new Books object, we just modify the
apply the onDelete() modifier to the ForEach view and remove from the model
properties of the Books object received by the view. The result is shown
the objects in the indexes received by the closure.
below.
Listing 10-71: Deleting objects
Figure 10-39: Interface to modify objects

struct ContentView: View {
@Environment(\.managedObjectContext) var dbContext
@FetchRequest(sortDescriptors: [], predicate: nil, animation: .default) var listOfBooks:
FetchedResults<Books>
} with the count property and assign the value to the totalBooks property to
.onDelete(perform: { indexes in
for index in indexes { show it to the user.
dbContext.delete(listOfBooks[index])
countBooks()
} Figure 10-40: Total number of books in the Persistent Store
do {
try dbContext.save()
} catch {
print("Error deleting objects")
}
})
}
.navigationBarTitle("Books")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: InsertBookView(), label: { 
Image(systemName: "plus")
})
} Do It Yourself: Update the ContentView view with the code in Listing
}
.onAppear {
10-72. Run the application on the iPhone simulator. You should see
countBooks() the total number of books on the screen. Slide a row to the left and
}
} press the Delete button. You should see the number on the screen
} go down by one unit.
func countBooks() {
let request: NSFetchRequest<Books> = Books.fetchRequest()
if let list = try? self.dbContext.fetch(request) { Although this is a legitimate way to count objects in the Persistent Store, it
totalBooks = list.count
} loads all the objects into memory and therefore it consumes too many
} resources. To avoid this issue, the NSManagedObjectContext class includes the
}
 count() method. This method returns an integer with the number of objects
we would get if we call the fetch() method with the same request. The
To store the number of books, this view defines a @State property called method does not fetch the objects, so we can call it without being afraid of
totalBooks, and to show the value, it includes an HStack view with two Text consuming too much memory. The following example improves the
views on top of the list. The request is created by a method called countBooks() method using the count() method.
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 Listing 10-73: Counting objects with the count() method
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 func countBooks() {
let request: NSFetchRequest<Books> = Books.fetchRequest()
request. In this case, we didn't define any predicate, so the array contains if let count = try? self.dbContext.count(for: request) {
all the Books objects in the Persistent Store. Finally, we count the objects totalBooks = count
} Image(systemName: "plus")
} })
 }
}
}
Do It Yourself: Update the countBooks() method from the previous }

example with the code in Listing 10-73. The method counts the Books
objects as before, but without consuming resources.
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
If what we want is to get the number of objects associated to a To-Many books property or show the value 0 if the property is equal to nil (no books
relationship, we just have to count the number of items returned by the have been assigned to the author).
property that represents the relationship. For example, we can count the
number of books of every author and show it along with the name.
Do It Yourself: Update the AuthorsView view with the code in Listing
10-74. Run the application on the iPhone simulator. Select a book
Listing 10-74: Counting the books of each author
and press the Select Author button. You should see the list of

struct AuthorsView: View {
authors available and the number of books assigned to each author
@FetchRequest(sortDescriptors: [], predicate: nil, animation: .default) private var listOfAuthors: on the right.
FetchedResults<Authors>
@Environment(\.dismiss) var dismiss
@Binding var selected: Authors? Another handy application of custom requests and the count() method is to
check for duplicates. Storing duplicated values is something every
var body: some View {
List { application should avoid. For example, if we insert an author that already
ForEach(listOfAuthors) { author in exists, two Authors objects with the same name will be stored in the
HStack {
Text(author.showName) Persistent Store. To avoid this situation, we can use a request with a
Spacer() predicate that looks for authors of the same name before creating a new
Text(String(author.books?.count ?? 0))
}
object. The following example modifies the InsertAuthorView view to check if
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: the author inserted by the user already exists in the Persistent Store.
.leading)
.background(.white)
.onTapGesture { Listing 10-75: Checking for duplicates
selected = author

dismiss()
} import SwiftUI
} import CoreData
}
.navigationBarTitle("Authors")
struct InsertAuthorView: View {
.toolbar { @Environment(\.managedObjectContext) var dbContext
ToolbarItem(placement: .navigationBarTrailing) { @Environment(\.dismiss) var dismiss
NavigationLink(destination: InsertAuthorView(), label: {
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.
Task(priority: .high) { property when the view appears. Otherwise, the content of the
await saveBook(title: newTitle, year: newYear!)
} 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.
}
.onAppear {
if let list = book?.author as? Set<Authors>, !valuesLoaded { Listing 10-84: Selecting multiple authors
selectedAuthors = Array(list) 
inputTitle = book?.title ?? ""
inputYear = book?.showYear ?? "" struct AuthorsView: View {
valuesLoaded = true @FetchRequest(sortDescriptors: [], predicate: nil, animation: .default) private var listOfAuthors:
FetchedResults<Authors>
}
@Environment(\.dismiss) var dismiss
}
@Binding var selected: [Authors]
}
func saveBook(title: String, year: Int32) async {
await dbContext.perform { var body: some View {
book?.title = title List {
book?.year = year ForEach(listOfAuthors) { author in
book?.author = NSSet(array: selectedAuthors) HStack {
Text(author.showName)
if selected.contains(where: { $0.name == author.name }) {
var letter = String(title.first!).uppercased() Image(systemName: "checkmark")
if Int(letter) != nil { .foregroundColor(Color.blue)
letter = "#" .frame(width: 25, height: 25)
} }
book?.firstLetter = letter }
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment:
.leading)
do {
.background(.white)
try dbContext.save()
.onTapGesture {
dismiss()
if selected.contains(where: { $0.name == author.name }) {
} catch {
if let index = selected.firstIndex(of: author) {
print("Error saving record")
selected.remove(at: index)
}
}
}
} else {
}
selected.append(author)
}
}

dismiss()
}
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 }
.navigationBarTitle("Authors")
user is coming from the ContentView view or the AuthorsView view. If the user .toolbar {
opened this view from the ContentView view, we need to load the list of ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: InsertAuthorView(), label: {
authors from the Persistent Store and assign them to the selectedAuthors Image(systemName: "plus")
}) we must tell the predicate to search for the value inside the set of authors.
}
} For this purpose, predicates can include the following keywords.
}
}
struct AuthorsView_Previews: PreviewProvider { ANY—This keyword returns true when the condition is true for some
static var previews: some View { of the values in the set.
AuthorsView(selected: .constant([]))
.environment(\.managedObjectContext, ApplicationData.preview.container.viewContext) 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
As always, this view displays all available authors. To show which one has the values in the set.
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 We have introduced predicate keywords earlier in this chapter. They are
row is tapped by the user, we check whether the author was previously included in the format string to determine the way the predicate filters the
selected or not. If it was selected, we remove it from the selectedAuthors data. For our example, we can add the ANY keyword in front of the
array, otherwise, we add it to it. This makes sure that the array only comparison to get the books associated with at least one author with a
contains the authors currently selected by the user. specific name, as shown next.
Do It Yourself: Open the Core Data model. Select the Books entity Listing 10-85: Fetching books by author

and change the Type of the author relationship to To-Many (Figure
.onAppear {
10-44). Update the Extensions.swift file with the code in Listing 10- let request: NSFetchRequest<Books> = Books.fetchRequest()
80, the ContentView view with the code in Listing 10-81, the request.predicate = NSPredicate(format: "ANY author.name == %@", "Stephen King")
if let list = try? dbContext.fetch(request) {
InsertBookView with the code in Listing 10-82, the ModifyBookView view for book in list {
print(book.title!)
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. 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
The To-Many to To-Many relationships also change the way we search for an author named "Stephen King". The predicate reads all the Authors
values. For instance, we cannot search for a book by author as we did objects in the relationship and returns the book when one of the names
before because now a book may be associated to many authors. Instead, matches the string.
All the views we have used so far are containers or present predefined SwiftUI allows us to create predefined or custom shapes. The following are
content on the screen, but SwiftUI also includes graphic views to create the views available to create standard shapes.
custom controls or to use for decoration. These views work like those
introduced before and can take advantage of most of the modifiers we Rectangle()—This initializer creates a Rectangle view. The size of the
have seen so far, but are specifically designed to draw custom graphics on rectangle is determined by the view's frame.
the screen. RoundedRectangle(cornerRadius: CGFloat, style:
RoundedCornerStyle)—This initializer creates a RoundedRectangle
view. The cornerRadius argument determines the radius of the
curvature of the corners, and the style argument is an enumeration of
type RoundedCornerStyle that determines the type of curvature to use.
The values available are circular and continuous. The view also includes
the following initializer to define the radius with a CGSize value:
RoundedRectangle(cornerSize: CGSize, style: RoundedCornerStyle).
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 the screen, scroll the views to the left. Use this project to test the
 rest of the examples in this chapter.
struct ContentView: View {
var body: some View {
VStack { By default, the views are rendered with a color depending on the
ScrollView(.horizontal, showsIndicators: true) { appearance mode (black for light and white for dark), but we can change
HStack {
Rectangle() the filling and stroke of the shapes with the following modifiers.
.frame(width: 100, height: 100)
RoundedRectangle(cornerRadius: 25, style: .continuous)
.frame(width: 100, height: 100) fill(View)—This modifier fills the shape with the view specified by
Circle() the argument. The argument is a view that represents a color, a
.frame(width: 100, height: 100)
Ellipse() gradient or an image.
.frame(width: 100, height: 50)
Capsule() stroke(View, lineWidth: CGFloat)—This modifier defines the
.frame(width: 100, height: 50) shape's border. The first argument is a view that represents a color, a
}.padding()
} gradient, or an image, and the lineWidth argument defines the
Spacer() border's width.
}
} stroke(View, style: StrokeStyle)—This modifier defines the shape's
}

border. The first argument is a view that represents a color, a gradient
or an image, and the style argument is a structure of type StrokeStyle
Figure 11-1: Standard shapes that defines the border's width, cap, join, miter limit, dash, and dash
phase.
strokeBorder(View, lineWidth: CGFloat)—This modifier defines
the shape's inner border. The first argument is a view that represents
a color, a gradient, or an image, and the lineWidth argument defines
the border's width.
strokeBorder(View, style: StrokeStyle)—This modifier defines the
shape's inner border. The first argument is a view that represents a

color, a gradient or an image, and the style argument is a structure of
Do It Yourself: Create a Multiplatform project. Update the ContentView type StrokeStyle that defines the border's width, cap, join, miter limit,
view with the code in Listing 11-1. If you don't see all the shapes on dash, and dash phase.
There are two aspects we can change with these modifiers, the filling of Listing 11-3: Defining a border
the shape and its border. The filling is defined by the fill() modifier and a 
view that represents the content, like a Color view. struct ContentView: View {
var body: some View {
HStack {
Listing 11-2: Filling a shape with a color RoundedRectangle(cornerRadius: 25)
.stroke(Color.red, lineWidth: 20)
 .frame(width: 100, height: 100)
struct ContentView: View { .padding()
var body: some View { RoundedRectangle(cornerRadius: 25)
RoundedRectangle(cornerRadius: 25) .strokeBorder(Color.red, lineWidth: 20)
.frame(width: 100, height: 100)
.fill(Color.red)
.padding()
.frame(width: 100, height: 100)
}
}
}
} }


Notice that the fill() modifier is implemented by the RoundedRectangle view, This view includes two RoundedRectangle views with a border of 20 points,
but the frame() modifier returns a different view, therefore all the modifiers but because we use different modifiers, the borders are different. Half of
defined for shapes, like fill(), must be applied before common modifiers like the border for the first rectangle is drawn outside the shape, while the
frame(). In this example, we use these modifiers to create a red rectangle
other half is drawn within the view's frame, but the border for the second
with rounded corners. rectangle is contained inside the frame.
 
Adding a border requires a similar process, but there are two types of These two modifiers can also take a StrokeStyle structure to fine-tune the
modifiers and they produce a slightly different result. The stroke() modifier border. The structure provides the following initializer.
expands the border outward and inward, while the strokeBorder() modifier
generates an inner border. StrokeStyle(lineWidth: CGFloat, lineCap: CGLineCap, lineJoin:
CGLineJoin, miterLimit: CGFloat, dash: [CGFloat], dashPhase:
CGFloat)—This initializer creates a StrokeStyle structure to configure a
stroke. The lineWidth argument determines the width. The lineCap
argument determines the style of the end of the lines. It is an
enumeration with the values butt (squared end), round (rounded end),
and square (squared end). The lineJoin argument sets the style of the

joint of two connected lines. It is an enumeration with the values miter
(sharp end), round (rounded end), and bevel (squared end). The
Shapes are views and therefore they can be combined with other SwiftUI
miterLimit argument determines how long the lines extend when the
views and controls. For instance, the following example assigns a Capsule
lineJoin argument is set to miter. The dash argument determines the
shape as the background of a button.
length of the segments for a dashed stroke. And the dashPhase
argument determines where the dashed line begins. Listing 11-5: Combining shapes with other views

The following example creates a RoundedRectangle view with the stroke struct ContentView: View {
configured as a dashed line with a width of 15 points and rounded caps. @State private var setActive: Bool = true
Gradients
background(Color, in: Shape)—This modifier assigns a shape to 
the background of a view. The first argument specifies the color, and
the in argument the shape. The filling and border of a shape can also be defined with gradients.
SwiftUI includes four structures designed to present gradients:
Using this modifier, we can declare the background of the button in the
LinearGradient, RadialGradient, AngularGradient, and EllipticalGradient. These
previous example with a single line of code.
structures conform to the ShapeStyle protocol, which defines the following
Listing 11-6: Combining shapes with other views methods to create customized instances.

.background(setActive ? Color.green : Color.red, in: Capsule()) linearGradient(Gradient, startPoint: UnitPoint, endPoint:
UnitPoint)—This method returns a linear gradient. The gradient
argument is the gradient of colors to be used, and the startPoint and
The button toggles the value of a @State property. If the value is true, we endPoint arguments determine the points inside the shape where the
show the label "Active" and assign a green capsule to the button's gradient starts and ends.
background, otherwise, we display the label "Inactive" and turn the
radialGradient(Gradient, center: UnitPoint, startRadius:
capsule red.
CGFloat, endRadius: CGFloat)—This method returns a circular
gradient. The gradient argument is the gradient of colors to be used.
Figure 11-5: Graphic button
The center argument determines the position of the center of the
circle, and the startRadius and endRadius arguments determine
where the gradient starts and ends.
gradient and the endAngle determines the angle at the end. common points. For example, we can apply a linear gradient with the
conicGradient(Gradient, center: UnitPoint, angle: Angle)—This values bottom and top to draw the gradient from the bottom to the top of
method returns a conic gradient. The gradient argument is the the shape.
gradient of colors to be used. The center argument determines the
Listing 11-7: Defining a linear gradient
position of the tip of the cone, and the angle argument determines

the angle where the gradient begins.
struct ContentView: View {
let gradient = Gradient(colors: [Color.red, Color.green])
These methods return an instance of one of the gradient structures
var body: some View {
introduced before, but the gradient of colors is defined by the Gradient RoundedRectangle(cornerRadius: 25)
structure. .fill(.linearGradient(gradient, startPoint: .bottom, endPoint: .top))
.frame(width: 100, height: 100)
}
Gradient(colors: [Color])—This initializer creates a gradient with }

the colors specified by the argument. The colors argument is an array
of Color views.
The code in Listing 11-7 defines a gradient with two colors, red and green,
Gradient(stops: [Gradient.Stop])—This initializer creates a and then applies the gradient to a RoundedRectangle view with the structure
gradient with the colors specified by the argument. The stops returned by the linearGradient() method. Because we declare the value bottom
argument is an array of Stop structures that determine the colors and as the starting point and the value top as the ending point, the colors are
when they stop. displayed from bottom to top in the order declared by the Gradient
structure.
Another value required to present a gradient is the UnitPoint structure. This
is like the CGPoint structure but specifically designed to work with graphic Figure 11-6: Linear gradient
structures.
When a gradient is created without specifying color stops, the colors are
evenly distributed throughout the area occupied by the gradient. If we Besides the linear gradient, we can also create gradients with different
want to customize the distribution, we must define the colors for the shapes. For instance, radial and elliptical gradients are created with circular
layers drawn from the center of a circle outwards, as shown next.
gradient with Stop structures.

Another type of gradients we can use for our shapes are the angular or Effects
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 The ShapeStyle protocol that defines the type methods to create gradient
Gradient structure, the center of the circle, and the angle where the gradient structures implemented in the previous section also defines multiple
begins. properties and methods to apply other effects to a view. The following are
the most frequently used.
Listing 11-10: Defining a conic gradient
 shadow(ShadowStyle)—This method applies a shadow to the view.
struct ContentView: View { The argument is a structure with two type methods to create drop
let gradient = Gradient(colors: [Color.red, Color.white])
and inner shadows: drop(color: Color, radius: CGFloat, x: CGFloat, y: CGFloat)
var body: some View { and inner(color: Color, radius: CGFloat, x: CGFloat, y: CGFloat).
RoundedRectangle(cornerRadius: 25)
.fill(.conicGradient(gradient, center: .center, angle: .degrees(180))) opacity(Double)—This method assigns to the view the level of
.frame(width: 100, height: 100)
}
opacity specified by the argument. The argument takes values from
} 0.0 (fully transparent) to 1.0 (fully opaque).

blendMode(BlendMode)—This method sets a blend mode that
The angles for the gradient are declared with an instance of the Angle determines how the view is going to blend with the background and
structure. This structure includes two type methods to defined the value in other views. The argument is an enumeration with the values normal,
degrees or radians: degrees(Double) and radians(Double). In the example of darken, multiply, colorBurn, plusDarker, lighten, screen, colorDodge, plusLighter,
Listing 11-10, we declare the beginning of the gradient at an angle of 180 overlay, softLight, hardLight, difference, exclusion, hue, saturation, color, luminosity,
degrees, which is the opposite side of the default starting point. sourceAtop, destinationOver, and destinationOut.
Figure 11-9: Conic gradient Many modifiers can take a structure that conforms to the ShapeStyle
protocol to assign a style to a view. When working with shapes, these
styles work better with the foregroundStyle() modifier. For instance, if we want
to apply a shadow to our rectangular shape, we can use this modifier for
the shadow and define the fill color with the foregroundColor() modifier, as we
do in the following example.

Listing 11-11: Adding a shadow to a view

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.
11.2 Paths
Figure 11-11: Pattern

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 of the circle formed by the arc, the radius argument is the length of
 the circle's radius, the startAngle and endAngle arguments are the
angles in which the arc starts and ends, and the clockwise argument
The Path view is designed to create a view that contains a custom path. The determines the orientation in which the arc is calculated (true
following are some of the initializers. clockwise and false counterclockwise).
addArc(tangent1End: CGPoint, tangent2End: CGPoint, radius:
Path()—This initializer creates an empty Path view. The path is created CGFloat)—This modifier adds an arc to the path using tangent points.
by applying modifiers to this instance. The tangent1End argument defines the coordinates of the end of the
Path(Closure)—This initializer creates an empty Path view. The first tangent line, the tangent2End argument defines the coordinates
argument is a closure to define the path. The closure receives a of the end of the second tangent line, and the radius argument
reference to the Path structure that we can use to create the path. determines the length of the circle's radius.
addCurve(to: CGPoint, control1: CGPoint, control2: CGPoint)
The path is created with a combination of lines and curves. The strokes —This modifier adds a cubic Bezier curve to the path with two control
move from one point to another in the view's coordinates, as if following points. The to argument defines the coordinates of the ending point,
the movement of a pencil. The Path structure defines a set of modifiers to and the control1 and control2 arguments define the coordinates of
determine the position of the pencil and generate the path. The following the first and second control points, respectively.
are the most frequently used. addQuadCurve(to: CGPoint, control: CGPoint)—This modifier
adds a quadratic Bezier curve to the path with a control point. The to
move(to: CGPoint)—This modifier moves the pencil to the argument defines the coordinates of the ending point, and the control
coordinates determined by the to argument.
argument defines the coordinates of the control point.
addLine(to: CGPoint)—This modifier adds a straight line to the addEllipse(in: CGRect)—This modifier adds an ellipse to the path.
path, from the pencil's current position to the coordinates indicated The in argument determines the area of the ellipse. If the rectangle is
by the to argument.
a square, the ellipse becomes a circle.
addLines([CGPoint])—This modifier adds multiple straight lines to addRect(CGRect)—This modifier adds the rectangle defined by the
the path. The lines are added in sequence according to the order of argument to the path. There is a version of this modifier that takes an
the points in the array. array of CGRect values to add multiple rectangles at a time
addArc(center: CGPoint, radius: CGFloat, startAngle: Angle, (addRects([CGRect])).
endAngle: Angle, clockwise: Bool)—This modifier adds an arc to addRoundedRect(in: CGRect, cornerSize: CGSize, style:
the path. The center argument specifies the coordinates of the center RoundedCornerStyle)—This modifier adds a rounded rectangle to
the path. The in argument determines the dimensions of the
rectangle, the cornerRadius argument determines the radius of the By default, the pencil's initial position is at the coordinates 0, 0 (top-left
curvature of the corners, and the style argument is an enumeration corner of the view). If we want our graphic to start from a different
with the values circular and continuous. position, we must apply the move() modifier first. In Listing 11-13, we move
the pencil to the coordinates 100, 150 before adding the first line.
A custom path works the same way as a predefined path. If we don't Subsequent lines are generated from the current position of the pencil to
specify the filling or the stroke, the path is drawn with a color that depends the coordinates indicated by the modifier. For instance, after setting the
on the appearance mode (black for light and white for dark), but we can initial point in our example, we create a line from that point to the point
change that with the fill() and stroke() modifiers, as we did before for 200, 150. Therefore, the next line starts at that point and ends at 100, 250.
standard shapes. If we paint the path with the fill() modifier, the path is Notice that we only created two lines. The line that goes from the point
automatically closed, but if we do it with the stroke() modifier, it remains 100, 250 to the point 100, 150 is generated automatically by the
open. To close the path and make sure all the lines are joined, the Path closeSubpath() modifier to close the path. If we want to create an open path,
structure includes the following modifier. we can ignore this modifier.
closeSubpath()—This modifier closes the current path. If the path is Figure 11-12: Custom path
not a closed path, the modifier adds a line between the end and the
beginning of the path to close it.
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. 
Listing 11-13: Defining a custom path Combining different modifiers, we can create complex paths. The following

path is defined with two lines and an arc.
struct ContentView: View {
var body: some View { Listing 11-14: Combining lines and arcs
Path { path in 
path.move(to: CGPoint(x: 100, y: 150))
path.addLine(to: CGPoint(x: 200, y: 150)) struct ContentView: View {
path.addLine(to: CGPoint(x: 100, y: 250)) var body: some View {
path.closeSubpath() Path { path in
}.stroke(Color.blue, lineWidth: 5) path.move(to: CGPoint(x: 100, y: 150))
} path.addLine(to: CGPoint(x: 200, y: 150))
} path.addArc(center: CGPoint(x: 200, y: 170), radius: 20, startAngle: .degrees(270),
 endAngle: .degrees(90), clockwise: false)
Figure 11-13: Lines and arcs In addition to addArc() and addEllipse(), we have two more modifiers to draw
curves. The addQuadCurve() modifier generates a quadratic Bezier curve, and
the addCurve() modifier generates a cubic Bezier curve. The difference
between these modifiers is that the first one has only one point of control
 and the second has two, thus creating different types of curves.
The addRect() and addEllipse() modifiers allow us to add rectangles and circles Listing 11-16: Creating complex curves
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 struct ContentView: View {
considered independent shapes. var body: some View {
Path { path in
path.move(to: CGPoint(x: 50, y: 50))
Listing 11-15: Combining lines and ellipses path.addQuadCurve(to: CGPoint(x: 50, y: 200), control: CGPoint(x: 100, y: 125))
path.move(to: CGPoint(x: 250, y: 50))
 path.addCurve(to: CGPoint(x: 250, y: 200), control1: CGPoint(x: 200, y: 125), control2:
struct ContentView: View { CGPoint(x: 300, y: 125))
var body: some View { }.stroke(Color.blue, lineWidth: 5)
Path { path in }
path.move(to: CGPoint(x: 100, y: 150)) }
path.addLine(to: CGPoint(x: 200, y: 150)) 
path.addEllipse(in: CGRect(x: 200, y: 140, width: 20, height: 20))
}.stroke(Color.blue, lineWidth: 5)
}
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 code in Listing 11-17 draws a triangle that is always half the width of
the curve, as shown below. 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
Figure 11-15: Complex curves 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()
The paths we have created so far use fixed values. This means that the modifier draws a line from this point to the point located at the right end
shape is always going to be of the same size, no matter the size of the view. of the triangle (posX + width). The next addLine() modifier draws a line from
To adapt the path to the size of the view, we must calculate how much this point to the point at the bottom left of the triangle (posY + height). And
space is available with the GeometryReader view (see Chapter 6). 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
Listing 11-17: Adapting the size of the path to the size of the container 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
struct ContentView: View { size of the screen.
var body: some View {
GeometryReader { geometry in
Path { path in
Figure 11-16: Path of relative size
let width = geometry.size.width / 2
let height = width
let posX = (geometry.size.width - width) / 2
let posY = (geometry.size.height - height) / 2
Custom Shapes

There are multiple tools provided by SwiftUI to change physical aspects of The code in Listing 11-20 displaces the image 75 points to the right (half
a view or a Shape view, like the orientation, perspective, or the position of the width of the Image view).
the content. The following are some of the modifiers available for this
purpose. Figure 11-18: Image displaced to the right
.rotation(.degrees(45)) }
.fill(Color.red) }
.frame(width: 100, height: 100) }
} 
}

This example implements the Triangle view defined in Listing 11-18. The first
Again, the transformation modifiers affect the content of the view, in this instance is displayed with a regular scale, but the second instance is
case the shape, but the view itself remains the same. In the example of transformed with the scale() modifier and a horizontal scale of -1, which
Listing 11-23, we create a RoundedRectangle view and rotate it 45 degrees. inverts the coordinate system, creating a mirror image.
 
The scale() modifier is not only used to resize the shape but also to achieve Do It Yourself: Update the ContentView view with the code in Listing
cool effects. For instance, we can contract or expand shapes by declaring 11-24. To test this example, you also need the Triangle.swift file we
different values for the horizontal and vertical scales, or create a mirror created before with the Triangle view defined in Listing 11-18. You
image by declaring a negative value. The following example implements should see the shapes in Figure 11-22 on the canvas.
this trick to invert the coordinate system and draw an inverted shape.
As we already mentioned, paths are drawn from one point to another, as if
Listing 11-24: Inverting a shape with the scale() modifier they were following the movement of a pencil. We can remove part of the
 process with the trim() modifier. This modifier determines what part of the
struct ContentView: View { path is drawn with values from 0.0 to 1.0, where 0.0 represents the
var body: some View { beginning of the path and 1.0 the end.
HStack {
Triangle()
.fill(Color.blue) Listing 11-25: Trimming a path
.frame(width: 100, height: 100)
Triangle() 
.scale(x: -1, y: 1) struct ContentView: View {
.fill(Color.blue) var body: some View {
.frame(width: 100, height: 100) HStack {
Triangle() 11.4 Canvas
.trim(from: 0, to: 0.70)
.stroke(Color.blue, lineWidth: 10)
.frame(width: 100, height: 100)

}
}
}
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
This example creates a Triangle view, but trims the path at the point 0.70, view specifically designed for dynamic 2D drawing.
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 Canvas(opaque: Bool, colorMode: ColorRenderingMode,
therefore the triangle is never finished. rendersAsynchronously: Bool, renderer: Closure)—This
initializer creates a Canvas view. The opaque argument determines
Figure 11-23: Incomplete shape whether the canvas is opaque (true) or transparent (false). The
colorMode argument defines the color space used to draw the
graphics. It is an enumeration with the values extendedLinear, linear, and
nonLinear. The rendersAsynchronously argument determines whether
the drawing is going to be made synchronously or asynchronously.
And the renderer argument provides the graphics to be drawn. The

closure receives two values, a GraphicsContext structure that represents
the graphic context where all the drawing is performed, and a CGSize
value with the canvas' width and height.
The following are some of the methods available to draw text. color(Color)—This method returns a color. The argument is the Color
view with the color we want to assign to the path.
draw(Text, at: CGPoint, anchor: UnitPoint)—This method draws color(red: Double, green: Double, blue: Double, opacity:
the text specified by the first argument. The at argument determines Double)—This method returns a color. The red, green, and blue
the position in the context, and the anchor argument determines the arguments determine the levels of red, green, and blue with values
text's origin. from 0.0 (no color) to 1.0 (full color).
draw(Text, in: CGRect)—This method draws the text specified by color(white: Double, opacity: Double)—This method returns a
the first argument within the area specified by the in argument. If color. The white argument determines the level of white with a value
there is not enough space in the area, the text is truncated. from 0.0 to 1.0 (black to white), and the opacity argument determines
the level of opacity with a value from 0.0 (transparent) to 1.0
And the following are the methods available to draw paths. (opaque). The opacity may be ignored.
linearGradient(Gradient, startPoint: CGPoint, endPoint:
stroke(Path, with: Shading, lineWidth: CGFloat)—This method
CGPoint)—This method returns a linear gradient. The first argument
draws the path specified by the first argument. The with argument
is the gradient of colors to be used, and the startPoint and endPoint
specifies the color or pattern used to stroke the path, and the
arguments determine the points inside the shape where the gradient
lineWidth argument determines the width.
starts and ends.
stroke(Path, with: Shading, style: StrokeStyle)—This method
radialGradient(Gradient, center: CGPoint, startRadius:
draws the path specified by the first argument. The with argument
CGFloat, endRadius: CGFloat)—This method returns a circular
specifies the color or pattern used to stroke the path, and the style
gradient. The first argument is the gradient of colors to be used. The
argument determines the style (see Listing 11-4).
center argument determines the position of the center of the circle,
fill(Path, with: Shading, style: FillStyle)—This method draws the and the startRadius and endRadius arguments determine where the
path specified by the first arguments and fills the shape. The with gradient starts and ends.
argument specifies the color or pattern used to fill the shape, and the
conicGradient(Gradient, center: CGPoint, angle: Angle)—This
style argument determines the style. It is a structure with the
method returns a conic gradient. The first argument is the gradient of
initializer FillStyle(eoFill: Bool, antialiased: Bool).
colors to be used. The center argument determines the position of
the tip of the cone, and the angle argument determines the angle values, we can define the CGRect necessary to present the image in a
where the gradient begins. smaller size.
tiledImage(Image, origin: CGPoint, sourceRect: CGRect, scale:
Listing 11-27: Drawing an image on the canvas
CGFloat)—This method draws the image specified by the first

argument over and over again to cover the shape. The origin struct ContentView: View {
argument determines the point in the shape where the initial image is var body: some View {
Canvas { context, size in
placed. The sourceRect argument determines the region of the original let imageSize = CGSize(width: 161, height: 216)
image we want to draw. And the scale argument defines the scale of let posX = (size.width - imageSize.width) / 2
let posY = posX
the image.
let imageFrame = CGRect(x: posX, y: posY, width: imageSize.width, height: imageSize.height)
By default, the canvas is non-opaque (the value false is assigned to the context.draw(Image("spot1"), in: imageFrame)
}.ignoresSafeArea()
opaque argument), the color mode is set to nonLinear, and the rendering is }
performed synchronously. If that configuration is good enough for our }

application, all we need is to declare the closure to render the graphics.
In this example, we define an area a quarter the size of the original image
Listing 11-26: Drawing an image on the canvas and divide the remaining horizontal space by 2 to determine its position.
 As a result, the image is centered on the canvas.
struct ContentView: View {
var body: some View {
Canvas { context, size in Figure 11-24: Image of a custom size
let imageFrame = CGRect(origin: .zero, size: size)
context.draw(Image("spot1"), in: imageFrame)
}.ignoresSafeArea()
}
}

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 Of course, we can also draw shapes, including standard and custom shapes
we use in this example is 644 pixels wide by 864 pixels tall. From these and paths. For instance, we can combine our image with some graphic and
text.
Listing 11-28: Drawing shapes and text scaleBy(x: CGFloat, y: CGFloat)—This method determines the
 horizontal and vertical scale of the canvas. By default, the scale is 1.0.
struct ContentView: View {
var body: some View { rotate(by: Angle)—This method rotates the canvas the angle
Canvas { context, size in specified by the by argument.
let imageFrame = CGRect(x: 60, y: 75, width: 215, height: 288)
context.draw(Image("spot1"), in: imageFrame) translateBy(x: CGFloat, y: CGFloat)—This method moves the
point of origin of the canvas to the position determined by the x and y
let circleFrame = CGRect(x: 20, y: 50, width: 60, height: 60)
context.fill(Circle().path(in: circleFrame), with: .color(.yellow)) arguments.
In this example, we create a banner on top of the image with a circle, a Listing 11-29: Rotating the canvas
rounded rectangle, and a text. The result is shown below. 
struct ContentView: View {
Figure 11-25: Shapes and text on the canvas var body: some View {
Canvas { context, size in
let imageFrame = CGRect(x: 60, y: 75, width: 161, height: 216)
context.draw(Image("spot1"), in: imageFrame)
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
The GraphicsContext structure also includes methods to perform
rotation, the previous image remains the same. This is because the
transformations on the canvas. The following are the most frequently used.
transformation methods affect the matrix by which the context calculates the image. In this example, we are showing the image at a quarter of its
how to draw the graphics, not the canvas itself. 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)
Figure 11-26: Rotation 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.
for _ in 0..<10 {
Listing 11-32: Preparing images context.rotate(by: .degrees(36))
 context.draw(Image("spot1"), in: imageFrame)
}
struct ContentView: View {
}.ignoresSafeArea() }.ignoresSafeArea()
} }
} }
 
 
In the previous examples, we keep modifying the context on top of prior The GraphicsContext structure also allows us to filter the graphics. With
modifications. If we want to work with multiple configurations, we can filters, we can add effects and configure multiple aspects of the graphics,
create and work on copies of the graphic context. The following example such as brightness, contrast, blurriness, and more. The structure includes
rotates an image in a copy of the context but then draws additional the following method to apply a filter.
graphics in the original context with a standard configuration.
addFilter(Filter)—This method applies a filter to the context. The
Listing 11-34: Working with different configurations argument defines the type of filter and its configuration. The structure
 includes multiple type methods to define standard filters. The most
struct ContentView: View {
var body: some View {
frequently used are brightness(Double), contrast(Double), saturation(Double),
Canvas { context, size in colorInvert(Double), colorMultiply(Color), hueRotation(Angle), grayscale(Double),
var copyContext = context
copyContext.translateBy(x: size.width/2, y: size.height/2) blur(radius: CGFloat), and shadow(color: Color, radius: CGFloat, x: CGFloat, y:
copyContext.rotate(by: .degrees(45)) CGFloat, blendMode: BlendMode, options: ShadowOptions).
let imageFrame = CGRect(x: -85, y: -108, width: 161, height: 216)
copyContext.draw(Image("spot1"), in: imageFrame)
Applying a filter is straightforward. We call the addFilter() method on the
let center = size.width/2 context for every effect we want to apply and all the graphics drawn
let rectFrame = CGRect(x: center - 125, y: 160, width: 250, height: 40)
context.fill(RoundedRectangle(cornerRadius: 25).path(in: rectFrame), with: .color(.yellow)) afterward will be affected. For instance, the following example tints an
image red and makes it blurry.
let textPos = CGPoint(x: center, y: 180)
context.draw(Text("My Picture").font(.title.bold()), at: textPos, anchor: .center)
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.
are assigned to the x axis and the name to the y axis). The result of both view to create the graphics. The RectangleMark view represents the values
configurations is shown below. with rectangles, and the AreaMark view fills the area below or between the
points.
Figure 11-32: Bar Charts
Figure 11-33: Line, Point, Rectangle, and Area charts
chartYScale(domain: Domain, range: Range, type: RuleMark(x: PlottableValue, yStart: PlottableValue, yEnd:
ScaleType?)—This modifier defines the scale for the y axis. The PlottableValue)—This initializer creates a view that represents data
domain argument specifies the possible values with a range for with a single line. The x argument determines the position of the line
quantitative values and an array for nominal values. The range
in the x axis, and the yStart and yEnd arguments determine the @EnvironmentObject var appData: ApplicationData
interpolationMethod(InterpolationMethod)—This modifier category with the foregroundStyle() modifier, as we did in the previous
determines the style used to join the points when the chart contains example for the bars, we also assign a different symbol to each category
lines or area marks. The argument is a structure with the type with the symbol() modifier, and finally enlarge the symbols with the
properties cardinal, catmullRom, linear, monotone, stepCenter, stepEnd, and symbolSize() modifier.
stepStart.
Figure 11-36: Multiple mark styles
chartForegroundStyleScale(Dictionary)—This modifiers assigns
specific foreground styles to a scale of values. The argument is a
dictionary with items that map the values to the styles.
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. 
Listing 11-42: Representing the values with different types of marks 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
struct ContentView: View { want to achieve. The following example shows a possible implementation.
@EnvironmentObject var appData: ApplicationData
In this model, we have a structure to store the date and the amount of
var body: some View { items sold (Sales), and another to store the sales per item (Products). To test
VStack { the application, we initialize it with two products, Bagels and Brownies,
Chart(appData.listOfItems) { item in
LineMark(x: .value("Name", item.name), y: .value("Calories", item.calories)) and a week of sales each.
.interpolationMethod(.catmullRom)
PointMark(x: .value("Name", item.name), y: .value("Calories", item.calories))
.foregroundStyle(by: .value("Category", item.category)) Listing 11-43: Representing the values with different types of marks
.symbol(by: .value("Category", item.category)) 
.symbolSize(200)
import SwiftUI
}.frame(height: 300)
.padding()
Spacer() struct Sales: Identifiable {
} let id = UUID()
} var date: Date
} var amount: Int
 }
struct Products: Identifiable {
let id = UUID()
In this example, we apply an interpolation of type catmullRom to the LineMark var name: String
view to smooth the lines. For the points, we apply a different color for each var sales: [Sales]
} }.frame(height: 300)
class ApplicationData: ObservableObject { .padding()
@Published var sales: [Products] Spacer()
}
}
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), This example creates a line chart. The first ForEach loop gets the products
Sales(date: Date(timeInterval: -86400 * 4, since: Date()), amount: 13),
Sales(date: Date(timeInterval: -86400 * 3, since: Date()), amount: 9), from the sales property in the model and then reads the sales property of
Sales(date: Date(timeInterval: -86400 * 2, since: Date()), amount: 7), each product to visualize the sales. Because we are using dates to define
Sales(date: Date(timeInterval: -86400 * 1, since: Date()), amount: 8) ]
let salesBrownies = [ the x axis, we implement the value() method that allows us to specify the
Sales(date: Date(timeInterval: -86400 * 7, since: Date()), amount: 3), date and the unit we want to use to represent the value (day). As a result,
Sales(date: Date(timeInterval: -86400 * 6, since: Date()), amount: 5),
Sales(date: Date(timeInterval: -86400 * 5, since: Date()), amount: 2), we get two line charts, one for Bagels and another for Brownies.
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), Figure 11-37: Multiple series of values
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. 
Listing 11-44: Visualizing two series of values 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
struct ContentView: View { shown in the following example.
@EnvironmentObject var appData: ApplicationData
var body: some View { Listing 11-45: Visualizing two series of values with a bar chart
VStack { 
Chart {
struct ContentView: View {
ForEach(appData.sales) { product in
@EnvironmentObject var appData: ApplicationData
ForEach(product.sales) { sale in
LineMark(x: .value("Date", sale.date, unit: .day), y: .value("Sales", sale.amount))
}.foregroundStyle(by: .value("Products", product.name)) var body: some View {
} VStack {
By default, the bars are added up to show the total, but the framework
offers the following modifier to customize the position.
11.6 Image Renderer

All the graphics and charts we have defined in previous examples are
created on the spot. If we want to recreate a graphic or a complex view, we
have to draw each line and circle again. But sometimes we may need to
 reuse a graphic or store it in a file or database. For cases like this, we can
convert the view to an image. SwiftUI includes the ImageRenderer class for
IMPORTANT: The Swift Charts framework includes additional tools to this purpose.
customize the charts and achieve any layout we need. There are also
initializers for the marks that we can use to provide maximum and ImageRenderer(content: View)—This initializer creates an image
minimum values to limit the graphics. The topic is beyond the scope from the view specified by the content argument.
of this book. For more information, visit our website and follow the
links for this chapter.
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.
newPicture
Button("Export Image") {
let renderer = ImageRenderer(content: newPicture)
if let img = renderer.uiImage {
pattern = img.preparingThumbnail(of: CGSize(width: 25, height: 25))
}
}
if let pattern {
Rectangle()
.fill(.image(Image(uiImage: pattern)))
.frame(width: 200, height: 200) 
}
Spacer()
} Do It Yourself: Create a Multiplatform project. Update the
}
}
ContentView.swift file with the code in Listing 11-47. Download the
struct NewPictureView: View { spot1 image from our website and add it to the Asset Catalog. Press
var body: some View {
Image("spot1") the Export Image button. You should see the rectangle filled with a
.resizable() pattern created from the new image (Figure 11-40).
.scaledToFit()
.frame(width: 150, height: 200)
.clipShape(Circle())
}
}

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.
the value 2 to the property, expanding the view, but because the delay(Double)—This method sets the seconds the animator waits
assignment is done within an animation closure, the change is animated. before starting the animation.
repeatCount(Int, autoreverses: Bool)—This method sets the
Figure 11-41: Animation by default 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 animation in this example is of type default, which on most systems is
the animator is going to perform the animation indefinitely. The
set as an easeInOut animation, but we can specify a different type if we think
autoreverses argument determines if the process of going back to the
it will look better on our interface. The following example applies a linear
initial state is going to be animated as well (true by default).
animation instead.
speed(Double)—This method sets the speed of the animation (1 by
Listing 11-49: Applying a linear animation default).

Button("Animate") { These methods are applied to the Animation structure, similar to how
withAnimation(.linear) { modifiers are applied to views. We create the Animation structure, call the
boxScale = 2
}
methods to configure the animation, and then apply it to the state with the
} withAnimation() method, as in the following example.

.repeatForever() 
withAnimation(animation) {
boxScale = 2
} 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
Do It Yourself: Update the Button view in the ContentView view with the are animated.
code in Listing 11-51. Press the Animate button. You should see the
square bounce indefinitely. Try different values for the Do It Yourself: Update the ContentView view with the code in Listing
interpolatingSpring() animation and implement other types of 11-52. Press the Animate button. You should see the square grow
animations to see how they work. and the corners become round, all happening at the same time.
The withAnimation() function can animate multiple states at a time. The In the last example, we applied the same animation to both values
system takes care of combining the animations, as shown in the following (easeInOut), but SwiftUI allows us to perform as many animations as we
example. need. All we have to do is to implement the withAnimation() function for each
value we want to animate with the type of animation we want to use. For
Listing 11-52: Animating multiple states at a time instance, the Button view in the following example performs two types of
 animations. We animate the roundCorners state with an easeOut animation,
struct ContentView: View { and the boxScale state with a linear animation. The effect is similar than
@State private var boxScale: CGFloat = 1
@State private var roundCorners: Bool = false before, but now each state is controlled by a different type of animation.
var body: some View { Listing 11-53: Applying a different animation to each state
VStack {
HStack { 
Rectangle() Button("Animate") {
.fill(Color.blue) withAnimation(.easeOut) {
.frame(width: 50, height: 50) roundCorners = true
.cornerRadius(roundCorners ? 15 : 0) }
.scaleEffect(boxScale) withAnimation(.linear) {
}.frame(width: 250, height: 120) boxScale = 2
Button("Animate") { }
withAnimation(.easeInOut(duration: 2)) { }
boxScale = 2 
roundCorners = true
}
}
}.padding()
}
}
Animating Custom Shapes number 0 or 1, so the system can generate values in between to animate
 the path (0.1, 0.2, 0.3, etc.).
}
}
func path(in rect: CGRect) -> Path {
let width = rect.width
let smileClamp = min(max(smile, 0), 1)
let section = rect.height / 5
let smilePos = section + (section * 3 * smileClamp) 
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.
create animations with a canvas that can be obtained from the animation simple class that we need to store and update the values. The class doesn't
property or the animation() method. There is a caveat, though. The system conform to the ObservableObject protocol in this case because we don't need
doesn't know what we want to animate on the canvas, so we must take to store any state, but the values could have also been stored in an
care of everything, including the frequency of the animation, the positions observable object or the model if necessary.
of the graphics, and more. The following example illustrates how to create The timing is controlled from the date produced by the TimelineView view.
a simple animation that repositions a circle on the screen after a few From this value, we get the current date, extract the seconds with the
fractions of a second. timeIntervalSinceReferenceDate property, check how many seconds has passed
since the last iteration, and update the position of the circle if the
Listing 11-57: Animating the canvas 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
class ContentViewData { current date in seconds to the lastTime property to know when the last
var posX: CGFloat = 0
var posY: CGFloat = 0 iteration happened, so the graphics are only updated every 0.2 seconds.
var lastTime: Double = 0
var maxTime: Double = 0.2
} Do It Yourself: Update the ContentView.swift file with the code in
struct ContentView: View { Listing 11-57. You should see a red circle appearing in different
let contentData = ContentViewData()
locations on the screen every 0.2 seconds. Repeat the process for
var body: some View { the following example.
TimelineView(.animation) { time in
let interval = time.date.timeIntervalSinceReferenceDate The previous example shows how to update the graphics on the canvas,
let delta = interval - contentData.lastTime
but it doesn't create any animation. To animate a graphic on the canvas,
Canvas { context, size in we must drawn every single frame. This means calculating the position of
if delta > contentData.maxTime { each graphic at every step of the animation. To demonstrate how this
contentData.posX = CGFloat.random(in: 0..<size.width - 20) works, we are going to animate a single circle at the center of the screen.
contentData.posY = CGFloat.random(in: 0..<size.height - 20)
contentData.lastTime = interval The animation is produced by controlling the circle's radius.
}
let circleFrame = CGRect(x: contentData.posX, y: contentData.posY, width: 20, height: 20)
context.fill(Circle().path(in: circleFrame), with: .color(.red)) Listing 11-58: Creating a real animation
}.ignoresSafeArea() 
} import SwiftUI
}
}
 class ContentViewData {
var radius: CGFloat = 0
var step: CGFloat = 5
Notice that we have defined a class to store the position of the circle and var lastTime: Double = 0
the required values to control the frequency of the animation. This is a var maxTime: Double = 0.02
} Transitions
struct ContentView: View {
let contentData = ContentViewData()

var body: some View {
TimelineView(.animation) { time in As we have seen before, we can add or remove views from the interface
let interval = time.date.timeIntervalSinceReferenceDate
let delta = interval - contentData.lastTime 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
Canvas { context, size in default, the system doesn't use any transition, it just displays or removes
if delta > contentData.maxTime {
calculateRadius() the view, but we can assign a specific transition to a view with the
contentData.lastTime = interval
}
following modifier.
let rad = contentData.radius
let circleFrame = CGRect(x: size.width/2 - rad, y: size.height/2 - rad, width: rad * 2,
height: rad * 2)
transition(AnyTransition)—This modifier assigns the transition
context.fill(Circle().path(in: circleFrame), with: .color(.red)) specified by the argument to the view.
}.ignoresSafeArea()
}
} SwiftUI defines standard transitions. The following are the type properties
func calculateRadius() {
contentData.radius = contentData.radius + contentData.step provided by the AnyTransition structure to create them.
if contentData.step < 0 && contentData.radius < 0 {
contentData.radius = 0
contentData.step = 5 opacity—This type property returns a transition that inserts or
}
if contentData.step > 0 && contentData.radius > 150 { removes a view by modifying its opacity.
contentData.radius = 150
contentData.step = -5 scale—This type property returns a transition that inserts or removes
} a view by modifying its scale.
}
} slide—This type property returns a transition that inserts or removes

a view by sliding it from or to the sides.
The process of controlling the frequency of the animation is the same as identity—This type property returns a transition that inserts or
before, but we have defined a function to calculate the radius. If the radius removes a view without any effect. This is the transition by default.
is small, we add 5 points to expand the circle, otherwise we begin the
opposite animation by reducing the radius by -5 points. To know whether The AnyTransition structure defines the type of transition, but for the
the circle is expanding or contracting, we store the difference in an transition to be applied, we must define the type of animation used to
additional property called step. When the value of this property is negative, produce it. The AnyTransition structure provides the following method for
it means that the circle is contracting, otherwise it is expanding. this purpose.
animation(Animation)—This method applies the animation iPhone simulator. Press the Show Information button. You should see
defined by the argument to the transition. the Text view appear or disappear from the screen every time the
button is pressed. Replace the scale transition by the opacity and slide
The animation() method is called on the instance of the AnyTransition types to see how they work.
structure, so we have to define the transition and then call this method to
determine the animation we are going to apply to it. In the following In the previous example, we use a standard animation (default). In this case,
example, we show and hide a Text view with a transition of type scale and an the code is easy to read, but it can become cumbersome when we apply
animation of type default. our own animations or combine custom transitions with custom
animations. To improve readability and organize the code, it is better to
Listing 11-59: Adding and removing a view with a transition define the transition externally. There are different ways to do it, but what
 is considered best practice is to declare an extension of the AnyTransition
struct ContentView: View {
@State private var showInfo = false structure and define our custom transition as if it belonged to the structure
itself. In the following example, we extend the AnyTransition structure to
var body: some View { define a custom transition called mytransition.
VStack {
Button("Show Information") {
showInfo.toggle()
}.padding()
Listing 11-60: Extending the AnyTransition structure
if showInfo { 
Text("This is the information") struct ContentView: View {
.transition(.scale.animation(.default)) @State private var showInfo = false
}
Spacer()
} var body: some View {
} VStack {
} Button("Show Information") {
 showInfo.toggle()
}.padding()
if showInfo {
The button in this view toggles the value of the showInfo property. When the Text("This is the information")
.transition(.mytransition)
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 Spacer()
}
reaches its natural size when the value of the property is true, and contracts }
}
until it disappears from the screen when it is false. extension AnyTransition {
static var mytransition: AnyTransition {
let animation = Animation.easeInOut(duration: 2)
Do It Yourself: Create a Multiplatform project. Update the ContentView let transition = AnyTransition.scale
view with the code in Listing 11-59. Run the application on the .animation(animation)
return transition
}
} For example, we can fade-in the view by modifying the opacity when it is
 added to the interface, and then remove it by reducing the scale.
The code in Listing 11-60 defines an extension of the AnyTransition structure Listing 11-61: Defining an asymmetric transition
with a type property called mytransition. This is a computed property that 
creates and returns a custom transition. Of course, we can define any extension AnyTransition {
static var mytransition: AnyTransition {
transition with any animation we want, the only requirement is that the let animation = Animation.easeInOut(duration: 2)
property returns a structure of type AnyTransition, so we can use it to specify let transition = AnyTransition.asymmetric(insertion: .opacity, removal: .scale)
.animation(animation)
the transition for a view. In this example, we create a custom easeInOut return transition
}
animation with a duration of 2 seconds and apply it to a standard transition }
of type scale. 
}.padding()
HStack {
if !showInfo {
Text("Left")
CHAPTER 12 - GESTURES
.matchedGeometryEffect(id: "TextAnimation", in: myAnimations)
}
Spacer()
if showInfo {
Text("Right")
.matchedGeometryEffect(id: "TextAnimation", in: myAnimations)
}
}.padding()
Spacer()
}
}
}

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.
Gestures are actions performed by the user on the screen, such as tapping, The most common gesture used in a mobile device is a tap gesture, which
swiping, or pinching. These gestures are difficult to detect because the only is detected when the user touches the screen with a finger. Because of
thing the screen returns is the position of the fingers. That is why Apple how common it is to work with this gesture, SwiftUI defines two
offers gesture recognizers. A gesture recognizer performs all the convenient modifiers to process it.
calculations necessary to recognize a gesture, so instead of processing
multiple events and values, we just wait for the notifications sent by the onTapGesture(count: Int, perform: Closure)—This modifier
system when complex gestures are detected and respond accordingly. recognizes a single or multiple taps. The count argument determines
how many taps are required for the gesture to be recognized (1 by
default), and the perform argument is the closure to be executed
when the gesture is detected. The closure receives a CGPoint value
with the location of the tap in the view coordinates.
onLongPressGesture(minimumDuration: Double,
maximumDistance: CGFloat, perform: Closure,
onPressingChanged: Closure)—This modifier recognizes a long
press gesture (the user keeps pressing the screen with a finger). The
minimumDuration argument is the time in seconds the user must
press the screen with the finger until the gesture is recognized. The
maximumDistance argument is the distance in points the user can
move the finger from the original position before the gesture is no
longer recognized. The perform argument is the closure to be
executed when the gesture is confirmed. And finally, the
onPressingChanged argument is the closure to be executed when the
user begins and ends pressing the view. The closure receives a
Boolean value to indicate whether the user is pressing or not.
didn't do in previous examples is get the position of the finger where the .scaledToFill()
.edgesIgnoringSafeArea(.all)
tap is made. This is a value of type CGPoint received by the closure with the }
x and y coordinates of the tap within the view. In the following example, }

we open a sheet when an image is tapped, and show how to access this
value.
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
Listing 12-1: Detecting a tap gesture on an image
screen, and when the user taps on it, it opens a sheet to show it in full size.

struct ContentView: View {
@State private var expand: Bool = false Figure 12-1: Image responding to the tap gesture
struct ShowImage: View { The long press gesture is similar to the tap gesture, but the system waits a
var body: some View {
Image("spot1") moment before confirming the gesture and performing the task. With the
.resizable()
modifier, we can set the waiting time and also perform a
onLongPressGesture() value 0 is assigned to the opacity() modifier, and when the user moves the
task while the user is pressing and waiting for the gesture to complete, as finger away, lifts the finger, or the gesture ends, the closure receives the
in the following example. 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.
Listing 12-3: Detecting a long press gesture Because the animation is longer than the waiting time for the gesture, the
 sheet is opened before the image completely disappears, which provides
struct ContentView: View { the necessary feedback for the user to know that it must wait for the
@State private var expand: Bool = false
@State private var pressing: Bool = false process to finish.
var body: some View { Do It Yourself: Update the ContentView view with the code in Listing
Image("spot1")
.resizable() 12-3. Press and hold the finger on the image (long click). You should
.scaledToFit() see the image fading-out and the sheet opening after 1 second.
.frame(width: 160, height: 200)
.opacity(pressing ? 0 : 1)
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
Hit Testing }
.allowsHitTesting(allowExpansion)
 .sheet(isPresented: $expand) {
ShowImage()
}
Because views may sometimes overlap and some may have implemented Toggle("", isOn: $allowExpansion)
.labelsHidden()
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
allowsHitTesting(Bool)—This modifier determines whether the is allowed on the Image view. The initial value of the property is set to false,
detection of hits is enabled on the view or not. so the user is not able to tap on the image to open the sheet, but when the
contentShape(Shape, eoFill: Bool)—This modifier defines the switch is turned on, the value true is assigned to the property and therefore
shape of the hitting area. The first argument is a shape view that the gesture is recognized by the Image view.
determines the area the user can interact with, and the eoFill
argument determines the algorithm to use to detect the hit. Do It Yourself: Update the ContentView view with the code in Listing
12-4. Run the application on the simulator. Tap on the image.
The allowsHitTesting() modifier can be used to disable a gesture. For instance, Nothing should happen. Turn on the switch below the image. Now,
we can enable or disable the tap gesture on the Image view of the previous the image should open the sheet when you tap on it.
example.
The contentShape() modifier also plays an important role in recognizing a
Listing 12-4: Disabling the tap gesture gesture. If we apply a gesture recognizer to an Image view or a Text view, the
 gesture is recognized when the user touches any part of the area occupied
struct ContentView: View { by the view. But this is not always the case. Container views, like VStack and
@State private var expand: Bool = false HStack, only recognize the gesture when it is performed on the area
@State private var allowExpansion: Bool = false
occupied by their content. To make sure that every part of the view can
var body: some View { recognize a gesture, we must force the content to occupy the entire area.
VStack(spacing: 20) {
Image("spot1")
We came across this issue before (see Listing 7-28). In those examples, we
.resizable() had to define a background with a Color view to provide a surface for the
.scaledToFit() tap gesture to be recognized. This was enough for our purpose, but it
.frame(width: 160, height: 200)
.onTapGesture { generates content that may not be required by the interface. A better
expand = true solution is to apply the contentShape() modifier. This modifier allows us to
define the hit surface for the gesture without adding any real content to The view in Listing 12-5 displays a row with information about a location. If
the view. the user taps anywhere on the row, the gesture recognizer toggles the
In the following example, we recreate the views we used in previous value of a @State property called selected, which we use to define the color
projects to create the rows of a list, but this time, instead of using a Color for the picture's border. The value true makes the border yellow (selected)
view to respond to the tap gesture, we define the row's content with a and the value false makes it transparent (deselected).
Rectangle view and the contentShape() modifier. This allows the user to tap
anywhere on the row to select it. Figure 12-2: Responsive row
recognizer to detect a long-press gesture. The minimumDuration argument is the closure to be executed every time the state is
argument is the time in seconds the user must press the screen with updated. The closure receives a value with information about the
the finger until the gesture is recognized. The maximumDistance state of the gesture, a reference to the Binding property, and a value of
argument is the distance in points the user can move the finger from type Transaction that contains information about the animation.
the original position before the gesture is no longer recognized.
Because of the frequency at which the updating() method is called, we can't
MagnificationGesture(minimumScaleDelta: CGFloat)—This
use a normal @State property to keep track of the state of the gesture. Any
initializer creates a gesture recognizer to detect a magnification
attempt to modify a state from inside the updating closure will return an
gesture. The minimumScaleDelta argument is the minimum error. Therefore, SwiftUI defines a specific property wrapper to work with
increment or decrement on the scale required for the gesture to be this method.
recognized.
RotationGesture(minimumAngleDelta: Angle)—This initializer @GestureState—This property wrapper stores the state of a
creates a gesture recognizer to detect a rotation gesture. The gesture and resets its value to its initial value when the gesture ends.
minimumAngleDelta argument is the minimum increment or
decrement on the angle of the view required for the gesture to be Once we have the instance of the gesture recognizer properly configured,
recognized. we must apply it to the view. The View protocol defines the following
modifiers for this purpose.
These initializers configure the gestures recognizers, but to respond to the
different states of the gestures, the structures implement the following
gesture(Gesture)—This modifier assigns a gesture recognizer to the Tap Gesture
view with a lower priority than the gesture recognizers already 
applied to the view.
highPriorityGesture(Gesture)—This modifier assigns a gesture Due to the simplicity of the tap gesture, there is not much difference
recognizer to the view with a higher priority than the gesture between applying the onTapGesture() modifier or implementing the TapGesture
recognizers already applied to the view. 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
simultaneousGesture(Gesture)—This modifier assigns a gesture
over time, it only uses the onEnded() method to perform a task when the
recognizer to the view that is processed along with the gesture
gesture is detected. The following example reproduces the previous
recognizers already applied to the view.
project, but this time we define the gesture recognizer with a TapGesture
structure.
The process is simple. We must instantiate a Gesture structure to define the
gesture recognizer, apply the onChanged(), onEnded() or updating() methods to Listing 12-6: Defining a TapGesture recognizer
the structure depending on what we want to do during the process, and 
assign that instance to the view with a modifier, like gesture(). What methods struct ContentView: View {
to apply depends on the gesture and what we want to achieve, and the @State private var expand: Bool = false
values received by these methods also depend on the type of gesture
var body: some View {
recognizer we are using. So there are multiple options available, as we will Image("spot1")
see next. .resizable()
.scaledToFit()
.frame(width: 160, height: 200)
.gesture(
TapGesture(count: 1)
.onEnded {
expand = true
}
)
.sheet(isPresented: $expand) {
ShowImage()
}
}
}

executed, and the value true is assigned to the expand property to open the Long Press Gesture
sheet. Notice that the onEnded() method is a method of the TapGesture

structure and therefore it is called on the instance of this structure, not on
the view.
Like the TapGesture structure, the LongPressGesture structure creates a simple
gesture recognizer, but in this case there is some activity while the gesture
Do It Yourself: For this example, you need the ShowImage view defined
is performed, so in addition to the onEnded() method, we can also
in Listing 12-2. Update the ContentView view with the code in Listing
implement the updating() method if we want to perform a task while the
12-6. You should see a small image on the screen. Tap the image to view is being pressed.
open the sheet. There are a few things we must considered when implementing the
updating() method. First, as mentioned before, this method requires a
@GestureState property instead of a @State property. A @GestureState property
stores the current state but it also resets itself to its initial value when the
gesture ends, so the initial value assigned to this property is the value we
want the property to always have as default. Second, we need to update
the state ourselves from the closure assigned to the method, but not
directly, we must do it from the reference received by the method (usually
called state). And third, because the change is not performed directly on the
state property, it can't be animated. To animate the process, we must
assigned the Animation structure to the animation property of the Transaction
structure produced by the gesture, as shown next.
.gesture(LongPressGesture(minimumDuration: 1)
.updating($pressing) { value, state, transaction in
state = value case inactive
transaction.animation = Animation.easeInOut(duration: 1.5)
} var isActive: Bool {
.onEnded { value in switch self {
expand = true case .active:
} return true
) case .inactive:
.sheet(isPresented: $expand) { return false
ShowImage() }
} }
} }
} struct ContentView: View {
 @GestureState private var pressingState = PressingState.inactive
@State private var expand: Bool = false
we can define a property of type PressingState and store an enumeration Magnification Gesture
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 The magnification gesture is often called the Pinch Gesture because it is the
@GestureState property directly, we get the Boolean value from the isActive gesture that is detected when the user spreads two fingers apart or brings
computed property defined by the enumeration. If the current value of the them together as if pinching the screen. This gesture is mostly
pressingState property is active, the isActive property returns true and the implemented to let the user zoom an image in and out.
opacity is set to 0, otherwise the value returned is false and the opacity is The value sent to the updating(), onChanged() and onEnded() methods is a CGFloat
set to 1. that represents a multiple of the scale that we must multiply by the current
The result is the same as before, but using enumeration values becomes scale to get the final scale of the picture, as in the following example.
necessary when working with more complex gestures or when multiple
gestures are combined. Listing 12-9: Defining a MagnificationGesture recognizer

struct ContentView: View {
@GestureState private var magnification: CGFloat = 1
@State private var zoom: CGFloat = 1
.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, .resizable()
.scaledToFit()
the magnification value is stored in the magnification property, but the value .frame(width: 160, height: 200)
of the zoom property is only modified when the gesture is over, so the next .scaleEffect(getCurrentZoom(magnification: magnification))
time the user tries to zoom the picture, the new scale is calculated from
.gesture(MagnificationGesture()
the last one. .updating($magnification) { value, state, transaction in
To set the scale of the image to the one selected by the user, we apply the state = value
}
scaleEffect() modifier to the Image view and calculate the new scale by .onEnded { value in
multiplying the value of the zoom property (the last scale set by the user) by zoom = getCurrentZoom(magnification: value)
}
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. func getCurrentZoom(magnification: CGFloat) -> CGFloat {
let minZoom: CGFloat = 1
let maxZoom: CGFloat = 2
Do It Yourself: Create a Multiplatform project. Download the spot1
var current = zoom * magnification
image from our website and add it to the Asset Catalog. Update the current = max(min(current, maxZoom), minZoom)
ContentView view with the code in Listing 12-9. Run the application on return current
}
a device. Pinch the view with two fingers to zoom in or out. If you }

run the application on the simulator, press the option key on your
keyboard to activate the gesture.
This example limits the scale of the view to a minimum of 1 and a
maximum of 2. Because there are a few operations we must perform to
The example in Listing 12-9 allows the users to zoom the image in and out
limit the scale to these values, we move the process to a method called
as far as they want, but most of the time we must limit the scale of the
getCurrentZoom() and call it every time necessary. The method defines two
view to values that make sense for the interface and the purpose of our
constants with the minimum and maximum values for the scale, then
application. To establish these limits, the scale must be controlled in two
calculates the current scale by multiplying the values of the zoom property
places: when it is applied to the view with the scaleEffect() modifier, and
by the magnification, and limits the result to a minimum of 1 and a
when the gesture ends and the final scale is assigned to the zoom property.
maximum of 2 with the max() and min() functions. The min() function
compares the scale with the maximum scale allowed and returns the
Listing 12-10: Determining a minimum and a maximum scale smallest value (if the value is greater than 2, it returns 2), and then the
 max() function compares the result with the minimum scale allowed and
struct ContentView: View {
@GestureState private var magnification: CGFloat = 1
returns the largest value (if the value is less than 1, it returns 1). The
@State private var zoom: CGFloat = 1 method is called by the scaleEffect() modifier to set the scale for the view,
and by the onEnded() method to set the final scale. Consequently, the user
var body: some View {
Image("spot1")
can zoom the image in and out, but up to a maximum of 2 and a minimum Rotation Gesture
of 1.

Do It Yourself: Update the ContentView structure with the code in
The rotation gesture is recognized when the user touches the screen with
Listing 12-10 and run the application. You should be able to scale the
two fingers and performs a circular motion. It is often used to rotate an
view to the limits set by the minZoom and maxZoom constants. 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
}
)
}
}

Drag and Drop Gesture
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
Drag and drop is an operation we can perform to move an element from
preserve the current state in case the user wants to rotate the image again
one app to another or between sections of the same app. This tool is
from that angle.
useful on devices that can share the screen with two or more windows, like
iPads and Mac computers. In Mac computers, the process is simple. We
Figure 12-3: Image rotated by the user
open two or more windows at the same time and use the mouse to drag
an element from one window to the other. On iPads, we must split the
screen in two. iPads include an icon with three dots at the top that we can
tap to share the screen with other applications.
The drag and drop gestures are performed on the views, but the data to be Figure 12-5: Drag and drop operation between apps
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. 
Listing 12-12: Allowing the user to drag an image Do It Yourself: Create a Multiplatform project. Download the husky
 image from our website and add it to the Asset Catalog. Update the
struct ContentView: View { ContentView view with the code in Listing 12-12. Run the application
var body: some View {
VStack { on the iPad simulator. Tap the three dots at the top of the screen and
Image("husky")
.resizable()
select the Split View option (Figure 12-4). Open the Photo Library.
.scaledToFit() You should see something like Figure 12-5. Open an album in the
.frame(width: 300, height: 400)
.draggable(Image("husky")) Photo Library and drag and drop the husky inside. The husky image
Spacer() should be added to the album.
}
}
}

The system creates an image from the view that is being dragged and use it modifier. The modifier takes a data type that determines the
dropDestination()
to show a preview to the user, but we can assign an additional view to the type of data the view can receive and a closure to process it. As before, the
draggable() modifier to provide a custom preview. For instance, the following type must conform to the Transferable protocol. For instance, we can use an
example shows an SF Symbol instead. Image view.
Listing 12-13: Providing a custom preview for the gesture Listing 12-14: Dropping images into an Image view
 
struct ContentView: View { struct ContentView: View {
var body: some View { @State private var picture: Image = Image("nopicture")
VStack {
Image("husky")
var body: some View {
.resizable()
VStack {
.scaledToFit()
picture
.frame(width: 300, height: 400)
.resizable()
.draggable(Image("husky"), preview: {
.scaledToFit()
Image(systemName: "scope")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 50))
.frame(height: 400)
})
Spacer() .dropDestination(for: Image.self, action: { elements, location in
if let image = elements.first {
}
picture = image
}
return true
}
}

return false
})
Figure 12-6: Custom preview Spacer()
}
}
}

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
In order for the user to be able to drop elements into our app, we must
of the operation. If we are able to get the values and process them, we
provide a view capable of receiving this data. To turn a view into a possible
must return true, otherwise false.
destination for a drag and drop operation, we must apply the
structure also includes two more initializers to only import or export var body: some View {
data: ProxyRepresentation(importing: Closure) and ProxyRepresentation(exporting: VStack {
Image(uiImage: picture)
Closure). .resizable()
.scaledToFit()
.draggable(ImageRepresentation(name: "My Picture", image: picture))
The purpose of these structures is to prepare the data to be sent and .dropDestination(for: Data.self, action: { elements, location in
process the data received in a drag and drop operation. Therefore, the if let data = elements.first, let image = UIImage(data: data) {
picture = image
structure we use to represent the data depends on what we want to return true
transfer. The CodableRepresentation structure is used to represent encodable }
return false
and decodable data, the DataRepresentation is used to represent raw data, like })
images, the FileRepresentation structure represents files, and the Spacer()
}
ProxyRepresentation structure is used to implement predefined
}
representations. }

No matter what structure we use, the values are transferred as generic
data. To let applications know how to process that data, we must declare
Our data type is a structure called ImageRepresentation with two properties:
the content type with a UTType structure. We have introduced this
name and image. For this application, we only want to transfer the image, so
structure before (see Chapter 10). As we will see later, we can define our
we get the structure to conform to the Transferable protocol, implement the
own types, but we can also use the standard types provided by the
transferRepresentation property, and define a DataRepresentation structure that
framework. In the following example, we use the png type to send a PNG
gets the data from the image and returns it.
image when the view is dragged.
Although we are transferring data representing an image, we use our own
custom data type to process it. When the user drags the view, the draggable()
Listing 12-16: Dragging custom values
modifier creates an instance of the ImageRepresentation structure and the

import SwiftUI
structure is sent to the closure assigned to the DataRepresentation structure,
so we can get the UIImage object in the image property, turn it into data with
the pngData() method, and return it. The external application receives this The UTType requires an identifier that we must create from settings. We
data, recognizes it as a PNG image thanks to the UTType structure, and need to go to the project's settings (Figure 5-4, number 6), open the Info
processes it as such. panel, expand the Exported Type Identifiers section, press the + button,
Notice that we have also applied the dropDestination() modifier, as we did in and insert the values.
previous examples, so the user can drag and drop images back and forward
between the application and external apps. Figure 12-9: Custom content type
var id = UUID()
var image: Data
var body: some View {
VStack {
static var transferRepresentation: some TransferRepresentation { HStack(spacing: 10) {
CodableRepresentation(for: PictureRepresentation.self, contentType: .product) ForEach(appData.listPictures) { picture in
} Image(uiImage: UIImage(data: picture.image) ?? UIImage(named: "nopicture")!)
} .resizable()
extension UTType { .frame(width: 80, height: 100)
static var product = UTType(exportedAs: "com.formasterminds.pictures") .draggable(picture)
} }
}.frame(height: 120)
Image(uiImage: currentPicture)
class ApplicationData: ObservableObject {
.resizable()
@Published var listPictures: [PictureRepresentation]
.scaledToFit()
.padding(10)
init() {
.dropDestination(for: PictureRepresentation.self, action: { elements, location in
listPictures = [
if let picture = elements.first {
PictureRepresentation(image: UIImage(named: "spot1")!.pngData()!),
currentPicture = UIImage(data: picture.image) ?? UIImage(named: "nopicture")!
PictureRepresentation(image: UIImage(named: "spot2")!.pngData()!),
appData.listPictures.removeAll(where: { $0.id == picture.id })
PictureRepresentation(image: UIImage(named: "spot3")!.pngData()!)
return true
]
}
}
return false
}
})
 }
}
After we define the PictureRepresentation structure to contain the data and }
struct ContentView_Previews: PreviewProvider {
extend the UTType to add our content type, we initialize the model with static var previews: some View {
three instances containing the images spot1, spot2, and spot3. The ContentView().environmentObject(ApplicationData())
}
purpose of this application is to allow the user to drag these pictures from }
the top of the screen to a larger view at the bottom. After a picture is 
dropped, we want to erase it from the list, and that's why, along with the
image, we include an identifier. The draggable() and dropDestination() modifiers work with the PictureRepresentation
For the interface, we need a ForEach loop to list all the images available at structure to transfer the data. When the user drags an image, the structure
the top and another Image view at the bottom where the user can drop encodes the data, including the identifier and the image, and when the
them. user drops the image into the target view, the data is decoded, an instance
of the PictureRepresentation structure is created, and we can process the
Listing 12-18: Dragging and dropping custom values values. In this case, we assign the image to the Image view and then remove
 the original picture from the list. The result is shown below.
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData Figure 12-10: Drag and drop with custom data
@State private var currentPicture: UIImage = UIImage(named: "nopicture")!
CHAPTER 13 - MAPKIT
Map(coordinateRegion: Binding, interactionModes: The latitude and longitude of a location are determined by a
MapInteractionModes, showsUserLocation: Bool, CLLocationCoordinate2D structure. The structure includes the following
The Map view shows the map in the region determined by the
When the user scrolls the map to a different area, the center of the region
MKCoordinateRegion structure assigned to the region property. In this case,
changes, and because we initialized the Map view with a bidirectional
binding, those values are assigned back to the region property, and @EnvironmentObject var appData: ApplicationData
therefore we can read them, process them, or show them to the user, as in
var body: some View {
this example. Map(coordinateRegion: $appData.region, interactionModes: .zoom)
.ignoresSafeArea()
}
Figure 13-2: Current latitude and longitude }

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 model now includes a second @Published property to store all the Figure 13-3: Annotation on the map
annotations to show on the map, so all we need to do is to add instances
of the PlaceAnnotation structure to this array and use this information to
display the annotations on the map. The Map view's initializer includes two
arguments for this purpose. The annotationItems argument specifies the
array with the values to create the annotations, and the
annotationContent argument defines the views to show each annotation
on the screen.
To display the annotations on the screen, we can use standard or custom
views. For instance, in the following example we show the annotations 
with a MapMarker view, which creates a standard view with the shape of a
balloon. Do It Yourself: Update the ApplicationData.swift file with the code in
Listing 13-5 and the ContentView view with the code in Listing 13-6.
Listing 13-6: Displaying the annotations Run the application on the iPhone simulator. You should see a pin
 indicating the location of the Apple store, as shown in Figure 13-3.
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
The MapMarker view creates a standard view with a predefined design, but
var body: some View { we can use any views we want to represent the annotations. All we need to
Map(coordinateRegion: $appData.region, annotationItems: appData.annotations,
annotationContent: { place in do is to provide the views with a MapAnnotation view instead.
MapMarker(coordinate: place.location, tint: .red)
})
.ignoresSafeArea()
Listing 13-7: Presenting the annotation with custom views
} 
} struct ContentView: View {
 @EnvironmentObject var appData: ApplicationData
The Map view in this example reads the array of PlaceAnnotation structures in var body: some View {
Map(coordinateRegion: $appData.region, annotationItems: appData.annotations,
the annotations property and calls the closure assigned to the annotationContent: { place in
annotationContent argument to create a MapMarker view to represent each MapAnnotation(coordinate: place.location) {
Circle()
annotation. .fill(Color.blue)
For testing, we included a single annotation in the model of Listing 13-5 .frame(width: 40, height: 40)
}
with the location of the Apple store, so the map shows a ballon to mark }).ignoresSafeArea()
that place. }
}


The MapAnnotation view in Listing 13-7 includes a 40 pixels by 40 pixels circle
painted in blue. The result is shown below. Figure 13-5: Custom image for annotations
Map(coordinateRegion: $appData.region, annotationItems: appData.annotations, Figure 13-6: Selected and deselected annotation
annotationContent: { place in
MapAnnotation(coordinate: place.location) {
VStack(spacing: 0) {
Image("iconmap")
.resizable()
.frame(width: place.selected ? 60 : 40, height: place.selected ? 60 : 40)
Text(place.name)
.font(.caption)
}
.onTapGesture {
for (index, item) in appData.annotations.enumerated() {
if item.id == place.id {
appData.annotations[index].selected.toggle() 
} else {
appData.annotations[index].selected = false
} Do It Yourself: Update the ContentView view with the code in Listing
}
} 13-9. Run the application on the iPhone simulator. Tap on the
}
}).ignoresSafeArea()
annotation. You should see the image expanding to a size of 60x60
} points, as shown in Figure 13-6.
}

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.
Local Search produced by the search.

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 MapKit framework incorporates a service to translate addresses into
The following are some of the properties included by this class to return
locations and find places of interest. The service is called Local Search and
the data from the place.
can take a freeform query string and return an array with the results. The
query is created from the Request class included in the MKLocalSearch class.
The following are some of the properties defined by the Request class to
name—This property sets or returns a string with the place’s name.
define the query. phoneNumber—This property sets or returns a string with the
place’s phone number.
naturalLanguageQuery—This property sets or returns a string with url—This property sets or returns a URL value with the URL of the
the term or address we want to search. place’s website.
region—This property sets or returns an MKCoordinateRegion structure placemark—This property sets or returns an MKPlacemark object with
that determines the region in which the search is performed. additional information about the place. The MKPlacemark class inherits
from the CLPlacemark class, which includes the location property. This
To perform a search, the MKLocalSearch class includes the following property stores a value of type CLLocation, which in turn includes the
initializer and method.
coordinate property we can use to retrieve the CLLocationCoordinate2D
structure with the place's latitude and longitude.
MKLocalSearch(request: MKLocalSearchRequest)—This
initializer creates an MKLocalSearch object to perform a search request.
There are different ways an app can perform a search and display the
start()—This asynchronous method performs a search and returns an results. As an example, we are going to search for places associated with
MKLocalSearchResponse object with the results. the word "Pizza" as soon as the view is loaded.
The search returns an MKLocalSearchResponse object that contains the Listing 13-10: Searching for pizza places
following properties. 
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
mapItems—This property returns an array of MKMapItem objects that
represent the results produced by the search. var body: some View {
Map(coordinateRegion: $appData.region, annotationItems: appData.annotations,
boundingRegion—This property returns an MKCoordinateRegion annotationContent: { place in
MapAnnotation(coordinate: place.location) {
structure that determines the region occupied by the results VStack(spacing: 0) {
Image("iconmap")
.resizable() the start() method. This method is asynchronous, so we must wait for the
.frame(width: 40, height: 40)
Text(place.name) results. Once the results are back from Apple servers, we remove the
.font(.caption) current annotations and create a loop to read all the places found and
.padding(3)
.background(RoundedRectangle(cornerRadius: 4).foregroundStyle(.thickMaterial)) store them in the model.
}
}
}).ignoresSafeArea() Figure 13-7: Places found by the Local Search system
.task(priority: .background) {
await setAnnotations()
}
}
func setAnnotations() async {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "Pizza"
request.region = appData.region
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
User Location

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. After authorization is granted, we may request a one-time report of the
We can ask permission to get updates only while the app is active (the app user's location or ask the manager to track the user and report the
is being used by the user at the time), or all the time (even when the app changes. For this purpose, the CLLocationManager class includes the following
moves to the background). The Core Location framework defines the property and methods.
CLLocationManager class to manage locations and get authorization from the
user. The following are some of the properties and methods included in location—This property returns a CLLocation object with information
this class for this purpose. about the user's location.
requestLocation()—This method requests a one time-delivery of
authorizationStatus—This property returns the current
the user's location.
authorization status. The value is an enumeration of type
CLAuthorizationStatus with the values notDetermined, restricted, denied,
startUpdatingLocation()—This method asks the manager to
authorizedAlways, and authorizedWhenInUse.
generate a report every time the user's location changes.
requestWhenInUseAuthorization()—This method asks for stopUpdatingLocation()—This method asks the manager to stop
authorization to get the location while the app is in use. reporting changes in the user's location.
requestAlwaysAuthorization()—This method asks for Every time the CLLocationManager object needs to report the user's location,
authorization to get the location when the app is active or in the it calls methods on a delegate object. The framework defines the
background. CLLocationManagerDelegate protocol to create this delegate. The following are
some of the methods included in the protocol.
For these methods to work, we must add an option to the app's
configuration to explain to the users why we need to access their location. locationManagerDidChangeAuthorization(CLLocationManage
The option is called "Privacy - Location When In Use Usage Description", r)—This method is called on the delegate when the authorization
and it is added from the Info panel, as we did for other options in Chapter status changes (the user grants or denies access to his or her
5 (see Figure 5-34).
location).
Figure 13-8: Privacy - Location When In Use Usage Description option locationManager(CLLocationManager, didFailWithError:
Error)—This method is called on the delegate when the manager fails
The class includes the following properties to read the values of the
notification.

name—This property returns the name of the notification.
object—This property returns a reference to the object that posted The model must store the values inserted by the user, as always, but also
the notification. read the notifications to update the main view.
userInfo—This property returns the dictionary attached to the Listing 14-1: Listening to notifications from the model
notification. 
import SwiftUI
The name of the notification is created from a structure included in the
Notification class called Name. The structure provides the following initializer class ApplicationData: ObservableObject {
@Published var total: Int = 0
to define custom names. var titles: [String] = []
The initial view shows the value of the total property on the screen and struct AddBook: View {
@EnvironmentObject var appData: ApplicationData
includes a button in the navigation bar to open a second view that allows @Environment(\.dismiss) var dismiss
the user to insert new values. @State private var titleInput: String = ""
Button("Save") { iPhone simulator. Press the Add Book button to go to the second
let title = titleInput.trimmingCharacters(in: .whitespaces)
if !title.isEmpty { scene. Insert a title and press the Save button. The view should be
addValue(title: title)
closed and the interface should show the total number of titles
dismiss()
}
inserted so far.
}
}
Spacer() The Notification object includes the userInfo property, which allows us to
}.padding() attach additional information to the notification. The values allowed to
.navigationBarTitle("Add Book")
}
include in the dictionary assigned to this property are Property List values
func addValue(title: String) { (NSNumber, NSString, NSDate, NSArray, NSDictionary, NSData, and the equivalents
appData.titles.append(title)
in Swift), but other than this, there are no more restrictions on what we
let center = NotificationCenter.default
can assign to this property. For instance, we could modify our previous
let name = Notification.Name("Update Data") example to pass the string inserted by the user and perform an additional
center.post(name: name, object: nil, userInfo: nil)
task when the notifications contain a specific title. The following are the
}
} changes we need to introduce to the addValue() method in the AddBook view
 to attach the title to the notification.
When a name is inserted into the TextField view and the Save button is Listing 14-4: Adding information to the notification
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 func addValue(title: String) {
assigned to the app, define a custom name for the notification ("Update appData.titles.append(title)
Data"), and post it with the post() method.
In the model, the asynchronous for in loop defined in the readNotifications() let center = NotificationCenter.default
let name = Notification.Name("Update Data")
method detects that there is a new notification available, performs a new let info = ["type": title]
cycle, the number of titles stored in the titles property is assigned to the total center.post(name: name, object: nil, userInfo: info)
}
property, and the initial view is updated with the new value. 
Do It Yourself: Create a Multiplatform project. Create a Swift file The code in Listing 14-4 declares a dictionary with the "type" key and the
called ApplicationData.swift for the model in Listing 14-1. Update the value inserted by the user and assigns it to the userInfo argument of the
ContentView view with the code in Listing 14-2. Create a SwiftUI View post() method. Now, we can check this value from our model every time a
file called AddBook.swift for the code in Listing 14-3. Remember to notification is received.
inject the ApplicationData object into the environment for the app and
Listing 14-5: Reading the value in the notification
the previews (Chapter 7, Listing 7-4). Run the application on the

func readNotifications() async {
let center = NotificationCenter.default
let name = Notification.Name("Update Data") Listing 14-6: Cancelling the task

for await notification in center.notifications(named: name, object: nil) { init() {
if let info = notification.userInfo {
let type = info["type"] as? String let myTask = Task(priority: .background) {
if type == "Miracle" { await readNotifications()
print("The Miracle title was inserted") }
} Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { timer in
} myTask.cancel()
await MainActor.run { }
total = titles.count }
} 
}
}
 Do It Yourself: Update the init() method in the ApplicationData class
with the code in Listing 14-6. Run the application on the iPhone
The values from the dictionary are returned as values of type Any, so we simulator. Press the Add Book button to insert a title. The initial view
must cast them to the right type. In the for in loop of Listing 14-5, we read should not count any new values after 10 seconds.
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.
The ScrollView view now moves up 20 points when the keyboard is opened
The asynchronous for in loop waits for new values to come in. This means and goes back to its original position when it is closed, generating a
that we cannot declared one loop after another because the second loop padding between the views and the keyboard.
will never be executed. This is the reason why in this example we have
created two tasks, one to listen to the keyboardDidShowNotification notification
Do It Yourself: Create a Multiplatform project. Create a Swift file
and another to listen to the keyboardDidHideNotification notification. When a
called ApplicationData.swift for the model in Listing 14-9. Update the
keyboardDidShowNotification notification is received, we assign the value -20 to
ContentView view with the code in Listing 14-10. Download the spot1
the scrollOffset property, and when a keyboardDidHideNotification notification is
received, we assign the value 0. image from our website and add it to the Asset Catalog. Remember
In the view, we can use the value of the scrollOffset property to set the to inject the ApplicationData object into the environment for the app
ScrollView view's offset. and the previews (Chapter 7, Listing 7-4). Run the application on the
iPhone simulator. Tap on the text field to activate the keyboard. You
Listing 14-10: Modifying the interface when the keyboard state changes should see the interface scrolling up and a padding between the
 views and the keyboard.
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
@FocusState var focusTitle: Bool IMPORTANT: The names of the keyboard notifications are provided
@State private var inputTitle: String = "" by the UIWindow class. The objects created from this class are used to
define the interface and therefore they only work on the main
var body: some View {
ScrollView { thread (the Main Actor). Therefore, to read these values, we must
VStack {
Image("spot1")
wait with the await keyword to avoid a data race (see Chapter 9).
.resizable()
.scaledToFit()
HStack { In Chapter 6, we determined the rotation of the device by detecting the
TextField("Insert Title", text: $inputTitle) Size Classes (see Listing 6-59) or by reading the values produced by the
.textFieldStyle(.roundedBorder)
GeometryReader view (see Listing 6-60). Although useful, these tools do not
.focused($focusTitle)
Button("Save") { apply to all situations. To know for certain the current orientation and
focusTitle = false
}
detect changes, we need direct access to the device. For this purpose, the
} UIKit framework defines the UIDevice class. This class creates an object that
Spacer()
}.padding()
controls the device and provides information about it, including the
} orientation. The class includes the following notification to report changes.
Listing 14-11: Detecting changes in the orientation
orientationDidChangeNotification—This notification is posted by 
the UIDevice object when the device's orientation changes. import SwiftUI
orientation: isPortrait, isLandscape, and isFlat. In the view, we need to read the isLandscape property to organize the views
according to the current orientation, but also call the UIDevice methods to
As always, we can listen to the notification from the model and perform get the system to start posting notifications when the device is rotated and
the necessary changes. In this cases, we have decided to include a to stop when is not required anymore. For this purpose, we can apply the
@Published property to update the views when the device's orientation goes onAppear() and onDisappear() modifiers, as shown next.

struct ContentView: View { This app is the same as the one created in Chapter 6 (see Listing 6-59), but
@EnvironmentObject var appData: ApplicationData
instead of organizing the views according to the horizontal Size Class, we
do it according to the value of the isLandscape property from the model.
var body: some View {
Group { Now the position of the views always depend on the orientation of the
if !appData.isLandscape { device.
VStack(spacing: 0) {
HeaderView(isCompact: true)
BodyView() Figure 14-4: Different interface for portrait and landscape orientations
}
} else {
HStack(spacing: 0) {
HeaderView(isCompact: false)
BodyView()
}
}
}.ignoresSafeArea()
.onAppear {
let device = UIDevice.current
device.beginGeneratingDeviceOrientationNotifications()
}
.onDisappear { 
let device = UIDevice.current
device.endGeneratingDeviceOrientationNotifications()
} Do It Yourself: Update the ApplicationData.swift file with the code in
}
}
Listing 14-11 and the ContentView.swift file with the code in Listing
struct HeaderView: View { 14-12. Run the application on the iPhone simulator. Rotate the
let isCompact: Bool
device. You should see different interfaces, as illustrated in Figure 14-
var body: some View { 4.
Text("Food Menu")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: isCompact ? 150 :
.infinity)
.background(Color.yellow)
}
}
struct BodyView: View {
var body: some View {
Text("Content Title")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color.gray)
}
}

14.2 User Notifications User Notifications Framework


User Notifications are created and managed by classes of the User
Another type of notification available is the User Notification. These are Notifications framework. The framework includes the UNUserNotificationCenter
notifications that the system shows to the user when the app has an event class to create a Notification Center that we can use to schedule and
to report, such as the completion of a task or real-life events that the user manage user notifications. This is like the Notification Center studied
wants to be reminded of. There are three different types of User before but specific for User Notifications. The class includes the following
Notifications: alert, badge, and sound. A badge-type notification displays a type method to retrieve the UNUserNotificationCenter object assigned to the
badge with a number over the app's icon, a sound-type notification plays a app.
sound, and an alert-type notification may be displayed as a banner, an
Alert View, or a message on the lock screen, depending on the current current()—This type method returns a reference to the
state of the device and the configuration set by the user. They can be UNUserNotificationCenter object assigned to the app.
scheduled all at once or independently. For instance, we can schedule a
notification that displays an alert and plays a sound, another that displays From the UNUserNotificationCenter object, we can manage the notifications.
an alert and shows a badge, or another that just plays a sound. The first step is to request authorization from the user. The class includes
the following methods for this purpose.
IMPORTANT: User Notifications are divided into Local Notifications
and Remote Notifications (also known as Push Notifications). Local requestAuthorization(options: UNAuthorizationOptions)—
Notifications are notifications generated by the application running This asynchronous method requests authorization from the user to
on the device, while Remote Notifications are generated by remote show notifications and returns a Boolean value to report the result.
servers and received by the system through the network. In this The options argument is a set of properties that determine the type
chapter, we are going to study Local Notifications. For more of notifications we want to show. The properties available are badge,
information on Remote Notifications, visit our website and follow sound, alert, carPlay, criticalAlert, provisional, and announcement.
For the notifications to be sent, they must be added to the User takes an object of the UNNotificationSound class. This class includes the
Notification Center. The UNUserNotificationCenter class includes the following following initializer and property to get the object.
methods to add and remove them.
UNNotificationSound(named: UNNotificationSoundName)—
add(UNNotificationRequest)—This asynchronous method This initializer creates a UNNotificationSound object with the sound
schedules a new notification in the User Notification Center. The specified by the named argument.
argument is the request for the notification. default—This type property returns a UNNotificationSound object with
removePendingNotificationRequests(withIdentifiers: the sound defined by the system.
[String])—This method removes the pending notifications with the
identifiers specified by the argument. The names of the sounds are defined by a structure of type
UNNotificationSoundName. The structure includes the following initializer.
}.disabled(isButtonDisabled)
}
Spacer()
}.padding()
.task(priority: .background) {
do {
let center = UNUserNotificationCenter.current()
let authorized = try await center.requestAuthorization(options: [.alert, .sound])
await MainActor.run {
isButtonDisabled = !authorized
}
} catch {
print("Error: \(error)") 
}
}
} 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
This view provides a TextField view for the user to insert a message to send the notificationSettings() method. We should always consult this method
before sending notifications to make sure that the app is still authorized to
with the notification and a button to post it. But before even enabling the
do it. If the value of the authorizationStatus property is equal to authorized, it
button, we must ask for permission with the requestAuthorization() method.
means that we can proceed. The add() method used to send a notification is
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 also asynchronous, so after the authorization is confirmed, we call our own
UNUserNotificationCenter object assigned to our app, ask the user to authorize
asynchronous method to send the notification.
the app to post notifications of type alert and sound, and then enable or
Listing 14-14: Scheduling a notification
disable the button on the interface according to the value returned.

(Notice that before the value is assigned to the isButtonDisabled property, it is
func sendNotification() async {
inverted with the ! symbol.) let content = UNMutableNotificationContent()
When the requestAuthorization() method is called, it creates an Alert View with content.title = "Reminder"
content.body = inputMessage
a message and two buttons to let the user decide what to do, as shown
below. let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false)
let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
Figure 14-5: Authorization to deliver notifications
do {
let center = UNUserNotificationCenter.current()
try await center.add(request)
await MainActor.run {
inputMessage = ""
}
} catch { Notifications can also play a sound. All we need to do is to add the sound
print("Error: \(error)")
} file to the project, create the UNNotificationSound object with it, and assign it
} to the sound property.

The process to schedule a notification is simple. We must create an Listing 14-15: Playing a sound

instance of the UNMutableNotificationContent class with the values we want the
func sendNotification() async {
notification to show to the user, create a trigger (in this case we use a Time let content = UNMutableNotificationContent()
Interval trigger), create an instance of the UNNotificationRequest class with content.title = "Reminder"
content.body = inputMessage
these values to request the delivery of the notification, and finally add the content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue:
request to the User Notification Center with the add() method. Notice that "alarm.mp3"))
the request identifier must be unique. In our example, we define it with a
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false)
string that includes the word "reminder" followed by a random value let id = "reminder-\(UUID())"
generated by the UUID() function. let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
Figure 14-6: Notification let center = UNUserNotificationCenter.current()
try await center.add(request)
await MainActor.run {
inputMessage = ""
}
} catch {
print("Error: \(error)")
}
}


Media Attachments
Listing 14-16: Attaching an image to a notification


func sendNotification() async {
In addition to sound, notifications can also include other types of media, let content = UNMutableNotificationContent()
such as images and videos. The UNMutableNotificationContent class includes the content.title = "Reminder"
content.body = inputMessage
following property to attach media files to the notification.
let idImage = "attach-\(UUID())"
attachments—This property sets or returns an array of if let urlImage = await getThumbnail(id: idImage) {
if let attachment = try? UNNotificationAttachment(identifier: idImage, url: urlImage,
UNNotificationAttachment objects with the media files we want to show in options: nil) {
content.attachments = [attachment]
the notification. }
}
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
The attachments are loaded by an object of the UNNotificationAttachment class.
The class includes the following initializer. let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
UNNotificationAttachment(identifier: String, url: URL, let center = UNUserNotificationCenter.current()
options: Dictionary?)—This initializer creates an attachment with try await center.add(request)
await MainActor.run {
the media loaded from the URL specified by the url argument. The inputMessage = ""
identifier argument is the attachment's unique identifier. And the }
} catch {
options argument is a dictionary with predefined values to configure print("Error: \(error)")
}
the media. The most useful are }
UNNotificationAttachmentOptionsThumbnailClippingRectKey to use only a func getThumbnail(id: String) async -> URL? {
let manager = FileManager.default
portion of an image, and UNNotificationAttachmentOptionsThumbnailTimeKey if let docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first {
to select a frame from a video. let fileURL = docURL.appendingPathComponent("\(id).png")
if let image = UIImage(named: "husky") {
if let thumbnail = await image.byPreparingThumbnail(ofSize: CGSize(width: 100, height:
To attach an image or a video to the notification, we must create the 100)) {
if let imageData = thumbnail.pngData() {
UNNotificationAttachment object and assign it to the content's attachments if let _ = try? imageData.write(to: fileURL) {
property. This object takes a unique identifier, that we can create as we return fileURL
}
always do, and the URL of the file that contains the media we want to add }
to the notification. This means that we need the media in a file or to create }
}
the file ourselves. The following example illustrates how to take an image }
from the Asset Catalog, store it in a file, and assign it to the notification. return nil
}
 Provisional Notifications
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 Asking the user for permission can be a bit disruptive for some
the image from the Asset Catalog, convert it to data, reduce the size with applications. If we consider that due to the characteristics of our app the
the byPreparingThumbnail() method, and store it (see Chapter 10, Listing 10- user's acceptance to receive notifications may be implicit, we can post
21). The file's URL is returned by the method and used by the code to provisional notifications. These are called quiet notifications and only show
attach the image to the notification. The result is shown below. The image up in the Notification Center (they are not displayed in the Locked or Home
is displayed inside the banner and expanded when the user taps and holds screens). They include buttons for the user to decide whether to keep
the finger over the notification (or drags the notification down, depending them or turn them off.
on the state of the application). 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.
Figure 14-7: Media Attachment
Listing 14-17: Scheduling provisional notifications

.task(priority: .background) {
do {
let center = UNUserNotificationCenter.current()
let authorized = try await center.requestAuthorization(options: [.alert, .sound, .provisional])
await MainActor.run {
isButtonDisabled = !authorized
}
} catch {
print("Error: \(error)")
 }
}

Do It Yourself: Update the sendNotification() method with the code in
Listing 14-16 and add the getThumbnail() method below. Download the If we use provisional notifications, the user is not prompted for
husky image from our website and add it to the Asset Catalog. Run authorization. The app is automatically authorized to post notifications, but
the application on the iPhone simulator. Insert a message and press the status is set as provisional instead of authorized, so we must consider this
the button to post the notification. Go to the Home screen. You condition when we check the status.
should see a notification with the picture of the husky, as in Figure
14-7. Listing 14-18: Checking the status of a provisional authorization

Button("Post Notification") {
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
Do It Yourself: Update the task() modifier in the ContentView view with
object with information about the notification and the action
the code in Listing 14-17 and replace the Button view with the view in
performed.
Listing 14-18. Uninstall the app and run it again from Xcode. Post a
When a notification is triggered and the app is being executed, the User
notification. Go to the Home screen and drag your finger from the
Notification Center calls the userNotificationCenter(UNUserNotificationCenter,
bottom to open the Notification Center. You should see the
willPresent:) method on its delegate to ask the application what to do. In this
provisional notification, as shown in Figure 14-8. 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. 
Listing 14-19: Showing notifications while the app is running Do It Yourself: Create a Swift file called ApplicationData.swift for the

model in Listing 14-19. Remember to inject the ApplicationData object
import SwiftUI
import UserNotifications into the environment for the app (Chapter 7, Listing 7-4). Remove all
the changes introduced in the previous section to create provisional
class ApplicationData: NSObject, ObservableObject, UNUserNotificationCenterDelegate { notifications or implement the ContentView view and the method
override init() { defined in Listings 14-13 and 14-14. Uninstall the app from the
super.init()
let center = UNUserNotificationCenter.current() simulator or the device. Run the application again, press the Allow
center.delegate = self
}
button, and post a notification. After a few seconds, the notification
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: should appear over the app at the top of the screen, as shown in
UNNotification) async -> UNNotificationPresentationOptions {
return [.banner] Figure 14-9.
}
}

Groups } catch {
print("Error: \(error)")
}
 }
}
await MainActor.run {
The system automatically groups notifications together by app. For inputMessage = ""
instance, if our application sends multiple notifications to the Notification }
}
Center, they will all be grouped together with the last one at the top. This

is the automatic behavior, but we can separate them in custom groups
using identifiers. The UNMutableNotificationContent class includes the following For didactic purposes, we defined one array with the name of the groups
property for this purpose. ("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
threadIdentifier—This property sets or returns a string used to include these values in the title and body of the UNMutableNotificationContent
identify each group of notifications. 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
All the notifications with the same identifier will be grouped together. The post six notifications in two groups, as shown below.
following example posts multiple notifications and separates them in two
groups called Group One and Group Two. Figure 14-10: Notifications in two groups
let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
let center = UNUserNotificationCenter.current()
try await center.add(request)
Summary available for the summary, the intentIdentifiers argument is an array
 of strings used to guide Siri to produce a better response, the
hiddenPreviewsBodyPlaceholder argument is the string to show
Users can create notification summaries to get a single alert at a specific instead of the notifications when the previews are disabled, the
time in the day with all the notifications grouped together. A summary can categorySummaryFormat argument is the string that describes the
include notifications from one or multiple apps. The tool that allows users summary, and the options argument is an array of properties that
to create a summary is available in the Notifications option of the Settings determine how the notifications associated to the category are going
app. to be handled. The properties available are customDismissAction
(processes the dismiss action) and allowInCarPlay (allows car play to
Figure 14-11: Summary option in Settings app show notifications).
The following are the modifications we need to introduce to the If we just run the application, the notifications are displayed as before, but
sendNotification() method to include the notifications in a summary. we can go to the Settings app and set a summary for the app, as shown in
Figure 14-11. Below is what the summary created by our app looks like
Listing 14-21: Configuring a summary of notifications when it is shown to the user.

func sendNotification() async { Figure 14-12: Notifications summary in the Lock screen
let center = UNUserNotificationCenter.current()
let groupID = "Group One"
let totalMessages = 3
UNTextInputNotificationAction(identifier: String, title: String, actionIdentifier—This property sets or returns a string with the
options: UNNotificationActionOptions, textInputButtonTitle:
action's identifier.
String, textInputPlaceholder: String)—This initializer creates an
notification—This property sets or returns a UNNotification object
action represented by a custom button that when pressed prompts
representing the notification. The object includes the date property to
the system to display an input field. In addition to the arguments
get the date the notification was delivered and the request property
included by a normal action, these types of actions also include the
with a reference to the UNNotificationRequest object used to schedule the
textInputButtonTitle and textInputPlaceholder arguments to define
notification, which in turn offers the content property to access the
the button and the placeholder for the input field.
values of the notification.
After the actions are defined, we must create a category to group them
together. The UNNotificationCategory class includes the following initializer to As an example, we are going to add an action that shows a Delete button
add actions to a notification. when the notification is opened. The following are the modifications we
must introduce to the sendNotification() method for this purpose.
@main
 struct TestApp: App {
@Environment(\.scenePhase) var scenePhase
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 Now that we have the delegate class and implemented a protocol method,
instantiated, and the app is ready to work. we must connect our SwiftUI app with it, so the method is called. For this
purpose, the SwiftUI framework includes the @UIApplicationDelegateAdaptor
application(UIApplication, configurationForConnecting:
property wrapper. This property wrapper is created from the
UISceneSession, options: UIScene.ConnectionOptions)—This
UIApplicationDelegateAdaptor structure, which includes the following initializer.
method is called when a new Scene (window) is requested by the
system or the user. The method must return a UISceneConfiguration
UIApplicationDelegateAdaptor(DelegateType.Type)—This
object with the Scene’s configuration.
initializer creates an instance of the UIApplicationDelegateAdaptor
application(UIApplication, didDiscardSceneSessions: Set)— structure associated with the class specified by the argument.
This method is called when the user discards a Scene (closes a
window). The didDiscardSceneSessions argument is a set with The @UIApplicationDelegateAdaptor property wrapper creates an instance of
references to the UISceneSession objects representing the Scenes' the class specified by the argument and assigns it as the app's delegate, so
sessions. when the app state changes, the methods on this object are called.
The most useful method defined by the UIApplicationDelegate protocol is Listing 14-28: Assigning the app delegate from a SwiftUI application
application(UIApplication, didFinishLaunchingWithOptions:). This method is called 
when the application is launched and therefore it is usually implemented import SwiftUI
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
object and then call any of these methods. The object takes care of
class ApplicationData: ObservableObject {
establishing the connection with iCloud and downloading or uploading the @Published var control: Double = 0
values, but it doesn't keep the values up-to-date. If a value is modified in let storage: NSUbiquitousKeyValueStore
one device, the change must be reflected on the others in real time. For
init() {
this purpose, the NSUbiquitousKeyValueStore class defines the following storage = NSUbiquitousKeyValueStore()
notification. control = storage.double(forKey: "control")
}
func valueChanged(value: Double) {
didChangeExternallyNotification—This notification is posted by if control != storage.double(forKey: "control") {
storage.set(value, forKey: "control")
the system when a change in the values on the Key-Value storage is storage.synchronize()
detected. }
}
func valueReceived() async {
As we already mentioned, the system is used to storing discrete values that let center = NotificationCenter.default
let name = NSUbiquitousKeyValueStore.didChangeExternallyNotification
represent the user’s preferences or the app’s status. For example, we may for await notification in center.notifications(named: name, object: storage) {
have a stepper that allows the user to set a limit on the number of items if notification.name == name {
await MainActor.run {
the application can manage. control = storage.double(forKey: "control")
}
}
Figure 15-4: Interface to test Key-Value storage }
}
}

 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
The application must initialize the interface with the current value stored in NSUbiquitousKeyValueStore object necessary to access the values in iCloud.
iCloud, but it also has to send the value back when a new one is set by the When the observable object is initialized, we create the
user, and update it when it is changed from another device. The best way NSUbiquitousKeyValueStore object and load from iCloud a value stored with the
to manage this information is from the model. We need a @Published "control" key. (If the value doesn't exist, the value returned is 0.)
property to store the local value and a few methods to send and receive Next, we define two methods. The valueChanged() method is executed every
the value to and from iCloud. 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()
Listing 15-1: Storing a value in iCloud 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 environment for the app and the previews (Chapter 7, Listing 7-4).
method is called valueReceived(). This method initializes an asynchronous for in Run the application on two different devices. Press the stepper’s
loop to listen to the didChangeExternallyNotification notification. When the buttons to change the value to 5 on one device. You should see the
value is modified from another device, the notification is triggered, so we value changing on the other device (the process may take several
can read the new value from iCloud and update the control property. seconds). Stop the application from Xcode and run it again. You
All the job is done by the model, so the view only needs to include a Stepper
should see the value 5 on the screen. (Remember to activate the
and a Text view to recreate the interface illustrated in Figure 15-4, and
same iCloud account in any of the simulators or devices you try.)
implement the onChange() and task() modifiers to call the methods in the
model when necessary.
IMPORTANT: The simulator does not update the information
Listing 15-2: Defining the interface to store and read values from iCloud automatically. Most of the time, you must force the update. If you
 modify the value on your device and do not see it changing on the
struct ContentView: View { simulator, open the Features menu at the top of the screen and
@EnvironmentObject var appData: ApplicationData select the option Trigger iCloud Sync. This will synchronize the
var body: some View {
application with iCloud and update the values right away.
VStack {
HStack {
The same way we can store one value, we can store several. The Key/Value
Stepper("", value: $appData.control)
storage service can manage multiple values using different keys, but if we
.labelsHidden()
Text("\(appData.control.formatted(.number.precision(.fractionLength(0))))")
want to simplify our work, we can use a structure to store all the values,
.font(.title) encode that structure into data with JSON, and then store only one value in
Spacer() iCloud containing that data. Our next example follows this approach. We
}
Spacer() define a structure to store the user's name, address and city, and then
}.padding() encode and decode an instance of that structure to store the values in
.onChange(of: appData.control) { value in
appData.valueChanged(value: value) iCloud.
}
.task {
await appData.valueReceived() Listing 15-3: Storing multiple values in iCloud
} 
}
import SwiftUI
}

struct PersonalInfo: Codable {
var name: String
Do It Yourself: Create a Swift file called ApplicationData.swift for the var address: String
model in Listing 15-1. Update the ContentView view with the code in var city: String
}
Listing 15-2. Remember to inject the ApplicationData object into the class ApplicationData: ObservableObject {
@Published var userInfo: PersonalInfo device. The only difference is in how the value is processed. This time we
let storage: NSUbiquitousKeyValueStore
are dealing with a structure encoded as a JSON value, therefore we need to
init() { use a JSONDecoder object when a value is received, and a JSONEncoder object
storage = NSUbiquitousKeyValueStore() 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
userInfo = PersonalInfo(name: "", address: "", city: "")
if let dataInfo = storage.data(forKey: "info") { another to let the user insert new ones. The initial view is simple. All it
let decoder = JSONDecoder() needs to do is to get the values from the @Published property, show them on
if let info = try? decoder.decode(PersonalInfo.self, from: dataInfo) {
userInfo = info
the screen, and initialize a task to update them every time they are
} modified from a different device.
}
}
func setInfo() { Listing 15-4: Reading multiple values from iCloud
let encoder = JSONEncoder()

if let data = try? encoder.encode(userInfo) {
storage.set(data, forKey: "info") struct ContentView: View {
} @EnvironmentObject var appData: ApplicationData
} @State private var openSheet: Bool = false
func valueReceived() async {
let center = NotificationCenter.default
var body: some View {
let name = NSUbiquitousKeyValueStore.didChangeExternallyNotification
NavigationStack {
for await notification in center.notifications(named: name, object: storage) {
VStack {
if notification.name == name {
HStack {
await MainActor.run {
Text("Name:")
if let dataInfo = storage.data(forKey: "info") {
let decoder = JSONDecoder() Text(appData.userInfo.name)
if let info = try? decoder.decode(PersonalInfo.self, from: dataInfo) { Spacer()
userInfo = info }
} HStack {
} Text("Address:")
}
Text(appData.userInfo.address)
}
} Spacer()
} }
} HStack {
 Text("City:")
Text(appData.userInfo.city)
Spacer()
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 Spacer()
}.padding()
to send the PersonalInfo structure to iCloud when the user inserts new .navigationBarTitle("Personal Info")
values, and another to listen to the didChangeExternallyNotification notification .toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
and update the structure when new values are inserted from a different Button("Change") {
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.
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
Do It Yourself: Create a Multiplatform project. Click on the app’s
another device. The application has to decide which version of the file to
settings option at the top of the Navigator Area (Figure 5-4, number
preserve or what data is more valuable when the file is edited from two
6) and open the Signing & Capabilities panel. Click on the + button at
different devices at the same time. These issues are not easy to solve and
the top-left corner of the panel to add a capability (Figure 15-1, can turn development into a nightmare. Considering all the problems a
number 1). Select the iCloud option, press return, and check the developer has to face, Apple introduced a class called UIDocument designed
option iCloud Documents. Press the + button to add a container specifically to manage files for iCloud. The class includes capabilities to
(Figure 15-6, number 1). Insert the app's bundle identifier for the coordinate and synchronize files of any size, and features that simplify the
container's name and press the OK button (you can find the bundle
manipulation of documents in mobile devices, like progression reports, open()—This asynchronous method asks the UIDocument object to
automatic thumbnail generation, undo manager, and others. open the file and load its content. The method returns a Boolean
The UIDocument class was not designed to be implemented directly in our value to indicate if the operation was successful.
code; it is like an interface between the app’s data and the files we use to
save(to: URL, for: SaveOperation)—This asynchronous method
store it. To take advantage of this class, we must create a subclass and
asks the UIDocument object to save the content of the document on file.
overwrite some of its methods. Once we define the subclass, we can create
the object with the following initializer. The for argument is an enumeration value that indicates the type of
operation to perform. The values available are forCreating (to save the
UIDocument(fileURL: URL)—This initializer creates a new file for the first time) and forOverwriting (to overwrite the file’s current
UIDocument object. The fileURL argument is a URL structure with the version). The method returns a Boolean value to indicate if the
location of the file in iCloud's container. operation was successful.
close()—This asynchronous method saves any pending changes and
The following are the methods we must override in the subclass of closes the document. The method returns a Boolean value to indicate
UIDocument to provide the data for the file and to retrieve it later. if the operation was successful.
contents(forType: String)—This method is called when the The first thing we have to do to work with documents in iCloud is to define
UIDocument object needs to store the content of the document on file. a subclass of UIDocument. The following is the one we are going to use for
The method must return an object with the document’s data (usually the examples in this chapter.
a Data structure). The forType argument identifies the type of the file
(by default, it is determined from the file’s extension). Listing 15-6: Creating the document

load(fromContents: Any, ofType: String?)—This method is called
import SwiftUI
when the UIDocument object loads the content of the document from
the file. The fromContents argument is an object with the file's class MyDocument: UIDocument {
var fileContent: Data?
content (usually a Data structure), and the ofType argument is a string
that identifies the file's type (by default, it is determined from the override func contents(forType typeName: String) throws -> Any {
return fileContent ?? Data()
file’s extension). }
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let data = contents as? Data, !data.isEmpty {
Once an object is created from our UIDocument subclass, we can manage the fileContent = data
file from the asynchronous methods provided by the class. The following }
are the most frequently used. }
}

Metadata Query
The UIDocument subclass needs at least three elements: a property to store

the file's content, the contents() method to provide the data to store in the
file, and the load() method to get the data back from the file. When the
Accessing the files is also complicated in iCloud. We cannot just get a list of
UIDocument object is asked to store or load the data in the file, it calls these
files with methods like contentsOfDirectory() from the FileManager class because
methods and use the property as a proxy to move the data around.
there could be some files that have not been downloaded yet to the
Therefore, every time we want to access the file's content, we must open
device. What we can do instead is to get the information pertaining to the
the document and read this property. In our example, we called it
files. This data is called Metadata, and refers to all the information
fileContent.
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 The codes listed below make up the model we need for this application.
array of NSMetadataItem objects. This is a simple class defined to contain the The first part, next, initializes the NSMetadataQuery object and starts listening
attributes of a file. The class provides the following method to retrieve the to the NSMetadataQueryDidFinishGathering notification to update the document
values. when new information is received.
value(forAttribute: String)—This method returns the value of the Listing 15-7: Initializing the model required to store a document in iCloud
file’s attribute determined by the forAtttribute argument. The 
NSMetadataItem class defines a list of constants to represent the import SwiftUI
Because of the logic of our application, we only need a property to store document = MyDocument(fileURL: documentURL)
document.fileContent = Data()
the reference to the MyDocument object we are going to use to access the if manager.fileExists(atPath: documentURL.path) {
file and a property to reference the NSMetadataQuery object that we will use let _ = await document.save(to: documentURL, for: .forOverwriting)
} else {
to search for the files available in the ubiquitous container. In this example, let _ = await document.save(to: documentURL, for: .forCreating)
we call these properties document and metaData. }
}
When the observable object is initialized, we create the NSMetadataQuery }
object, configure the query to search for documents inside the iCloud's }
container (NSMetadataQueryUbiquitousDocumentsScope), start listening to the 
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() Listing 15-13: Processing the list of files in the container
}
} 
} @MainActor
Task(priority: .high) { func getFiles() {
let center = NotificationCenter.default if metaData.resultCount > 0 {
let name = NSNotification.Name.NSMetadataQueryDidUpdate let files = metaData.results as! [NSMetadataItem]
for await notification in center.notifications(named: name, object: metaData) { for item in files {
if notification.name == name { let fileName = item.value(forAttribute: NSMetadataItemFSNameKey) as! String
let wrapper = NotificationWrapper(value: notification)
await updateFiles(notification: wrapper) if !listOfFiles.contains(where: { $0.name == fileName }) {
} let documentURL = item.value(forAttribute: NSMetadataItemURLKey) as! URL
} listOfFiles.append(FileInfo(name: fileName, url: documentURL))
} }
metaData.start() }
} listOfFiles.sort(by: { $0.name < $1.name })
} }
 }

The model in Listing 15-12 includes a new structure called FileInfo and a
The getFiles() method includes a for in loop to go through the list of
@Published property called listOfFiles to store the instances that represent the
documents retrieved by the query, get the name of each file, check
documents in the container. In addition to the
whether the listOfFiles array already contains a file with that name, and add
NSMetadataQueryDidFinishGathering notification, now we also listen to the
it to the list if necessary.
NSMetadataQueryDidUpdate notification to keep the files up to date. When this
As we already mentioned, another method we need to add to the model is
notification is received, we execute a method called updateFiles(). This
the one called when a NSMetadataQueryDidUpdate notification is received. This
method receives the Notification object produced by the notifications() method
notification is posted every time the NSMetadataQuery object detects an
to know the changes it needs to perform in the model. Notice that the
update in the container (local or remote). In this case, we must respond to
method is executed in the Main Actor, but the Notification object comes from
a document being added or removed. To detect the type of change, the
an asynchronous thread. This means that the object is not safe (it can
NSMetadataQuery class defines the following constants.
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
NSMetadataQueryUpdateAddedItemsKey—This constant
Sendable protocol, read Chapter 9.)
retrieves an array of NSMetadataItem objects that represent the
The rest of the code in the observable object is the same as before, only documents added to the container.
the methods change. For instance, when the NSMetadataQueryUpdateChangedItemsKey—This constant
NSMetadataQueryDidFinishGathering notification is posted by the NSMetadataQuery retrieves an array of NSMetadataItem objects that represent the
object, we execute a method called getFiles() to process the list of files documents that were modified.
available in the container.
NSMetadataQueryUpdateRemovedItemsKey—This constant }
metaData.enableUpdates()
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
This information is returned by the userInfo property of the Notification object
Notification object, so we can read the notification's userInfo property and
produced by the NSMetadataQueryDidUpdate notification and passed to the
determine the type of updates performed by the user. If the value is of
updateFiles() method, so we can process the changes accordingly, as shown
type NSMetadataQueryUpdateRemovedItemsKey, we remove the deleted
next.
documents from the listOfFiles array, but if the value is of type
NSMetadataQueryUpdateAddedItemsKey, we create a FileInfo structure to
Listing 15-14: Updating the container
represent the file and add it to the listOfFiles array as before.

Notice that to avoid simultaneous updates, we disable the NSMetadataQuery
@MainActor
func updateFiles(notification: NotificationWrapper) { object momentarily with the disableUpdates() method and enable it again
metaData.disableUpdates() with the enableUpdates() method when the updates are over.
The remaining methods are those required by the views to process the
let manager = FileManager.default
documents. For instance, we need the following two methods to create
if let modifications = notification.value.userInfo {
and remove files from the container.
if let removed = modifications[NSMetadataQueryUpdateRemovedItemsKey] as?
[NSMetadataItem] {
Listing 15-15: Adding and removing documents from the container
for item in removed {
let name = item.value(forAttribute: NSMetadataItemFSNameKey) as! String 
if let index = listOfFiles.firstIndex(where: { $0.name == name }) { @MainActor
listOfFiles.remove(at: index) func createFile(name: String) async {
} let manager = FileManager.default
} if let fileURL = manager.url(forUbiquityContainerIdentifier: nil) {
} let documentURL = fileURL.appendingPathComponent("Documents/\(name)")
if let added = modifications[NSMetadataQueryUpdateAddedItemsKey] as? [NSMetadataItem] let document = MyDocument(fileURL: documentURL)
document.fileContent = Data()
{ let _ = await document.save(to: documentURL, for: .forCreating)
for item in added { }
let name = item.value(forAttribute: NSMetadataItemFSNameKey) as! String }
if !listOfFiles.contains(where: { $0.name == name }) { func removeFiles(indexes: IndexSet) async {
if let fileURL = manager.url(forUbiquityContainerIdentifier: nil) { let manager = FileManager.default
let documentURL = fileURL.appendingPathComponent("Documents/\(name)") for index in indexes {
listOfFiles.append(FileInfo(name: name, url: documentURL)) let fileURL = listOfFiles[index].url
} do {
} try manager.removeItem(atPath: fileURL.path)
}
await MainActor.run {
listOfFiles.sort(by: { $0.name < $1.name })
let _ = listOfFiles.remove(at: index)
}
}
method removes the files selected by the user from the container and the
corresponding FileInfo structures from the array. This last method receives The model is ready, now we must define the views. For this example, we
an IndexSet value with the indexes of the documents to be removed. This is need three views: one to list the documents available, one to let the user
the value generated by the onDelete() modifier. We iterate through these add new documents, and another to edit them. The following are the
values with a for in loop, get the file's URL from the url property, and changes we need to introduce to the ContentView view.
remove it from the container with the removeItem() method and from the
listOfFiles array with the remove() method. Notice that the method is
Listing 15-17: Listing the documents available in the container

asynchronous. This is required by the system. Every time we remove a file
struct ContentView: View {
from the ubiquitous container we must do it from a background thread. @EnvironmentObject var appData: ApplicationData
And this is the reason why we modify the listOfFiles property from the Main @State private var openSheet: Bool = false
Actor. (Changes to the interface must always be performed from the main
thread.) var body: some View {
NavigationStack {
As before, the model also needs a few more methods to open, save, and List {
close the documents. ForEach(appData.listOfFiles) { file in
NavigationLink(destination: EditDocumentView(selectedFile: file)) {
Text(file.name)
Listing 15-16: Opening, saving, and closing a document }
 }
.onDelete { indexes in
@MainActor Task(priority: .background) {
func openDocument(url: URL) async -> String { await appData.removeFiles(indexes: indexes)
document = MyDocument(fileURL: url) }
let success = await document.open() }
if success { }
if let data = document.fileContent { .navigationBarTitle("List of Files")
return String(data: data, encoding: .utf8) ?? "" .navigationBarTitleDisplayMode(.inline)
} .toolbar {
} ToolbarItem(placement: .navigationBarTrailing) {
return "" Button("Create File") {
} openSheet = true
@MainActor }
func saveDocument(url: URL, content: String) async {
} TextField("Insert name and extension", text: $inputFileName)
}
.sheet(isPresented: $openSheet) { .textFieldStyle(.roundedBorder)
CreateFileView() .autocapitalization(.none)
.disableAutocorrection(true)
}
.padding()
}
Spacer()
}
}
}
}

}

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, This view includes a TextField view to let the user insert the name of the
and includes a button in the navigation bar to open a sheet to add new document and a button to save it. When the user presses the Save button,
documents. The view opened by the sheet() modifier is called CreateFileView. 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
Listing 15-18: Creating new documents 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
struct CreateFileView: View { rows. This navigation link opens the EditDocumentView view to allow the user
@EnvironmentObject var appData: ApplicationData
@Environment(\.dismiss) var dismiss
to edit the selected document. The following are the changes we need to
introduced to this view for our example.
@State private var inputFileName: String = ""
@State private var buttonDisabled: Bool = false
Listing 15-19: Displaying the document's content

var body: some View {
VStack { struct EditDocumentView: View {
HStack { @EnvironmentObject var appData: ApplicationData
Button("Close") { @Environment(\.dismiss) var dismiss
dismiss() @State private var inputText: String = ""
}
Spacer()
Button("Create") { let selectedFile: FileInfo
let fileName = inputFileName.trimmingCharacters(in: .whitespaces)
if !fileName.isEmpty && !appData.listOfFiles.contains(where: { $0.name == fileName }) {
var body: some View {
buttonDisabled = true
GroupBox {
TextEditor(text: $inputText)
Task(priority: .high) { }
await appData.createFile(name: fileName) .navigationBarTitle(selectedFile.name)
dismiss() .navigationBarTitleDisplayMode(.inline)
} .toolbar {
} ToolbarItem(placement: .navigationBarTrailing) {
}.disabled(buttonDisabled) Button("Save") {
}.padding() Task(priority: .high) {
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.

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. 
Enabling CloudKit when something changes in a database. Because this may happen not only
when the user is working with the app but also when the app is in the

background, to get these notifications, we must add the Background Mode
capability and activate two services called Background Fetch and Remote
As we did with the rest of the iCloud services, the first step to use CloudKit
Notifications.
is to activate it from the Signing & Capabilities panel.
Figure 15-14: Background Mode
Figure 15-12: CloudKit service


CloudKit requires a container to manage the databases. If the container is
not automatically generated by Xcode when we activate the CloudKit Do It Yourself: Create a Multiplatform project. Click on the app’s
service, we must create it ourselves, as we did before for the iCloud settings option at the top of the Navigator Area (Figure 5-4, number
Documents service (see Figures 15-6 and 15-7). 6) and open the Signing & Capabilities panel. Click on the + button at
Because CloudKit uses Remote Notifications to report changes in the the top-left corner of the panel to add a capability. Select the iCloud
databases, when we activate CloudKit, Xcode automatically includes an option and press return. Repeat the process to add the Background
additional service called Push Notifications. Modes capability. In the Background Modes section, check the
options Background fetch and Remote Notifications (Figure 15-14).
Figure 15-13: Push Notifications
In the iCloud section, check the option CloudKit (Figure 15-12). Press
the + button to add a container. Insert the app's bundle identifier for
 the container's name and press the OK button (you can find the
bundle identifier at the top of the panel). If the name of the
Remote Notifications are like the Local Notifications introduced in Chapter container appears in red, press the Refresh button to upload the
14, but instead of being posted by the app they are sent from a server to information to Apple servers.
inform our app or the user that something changed or needs attention.
The Remote Notifications posted by CloudKit are sent from Apple servers
Implementing CloudKit Data objects specified by the for argument. The argument is an array
 of identifiers (returned by the objectID property).
recordID(for: NSManagedObjectID)—This method returns a
Although we can access a CloudKit database and manually create, modify CKRecord.ID value with the identifier of the record that corresponds to
and delete records, as we will see later, this requires us to take care not the Core Data object specified by the for argument. The argument is
only of the process of keeping the database up to date, but also check for the object's identifier (returned by the objectID property).
errors and synchronize devices. Because these tasks are usually the same
recordIDs(for: [NSManagedObjectID])—This method returns an
for most applications, Apple provides an API that works along with Core
array of CKRecord.ID values with the identifiers of the records that
Data to automatically share the data stored on the device with a CloudKit
correspond to the Core Data objects specified by the for argument.
database. All we need to do is to create the Core Data stack with the
NSPersistentCloudKitContainer class instead of the NSPersistentContainer class.
The argument is an array of object identifiers (returned by the objectID
After this, the Core Data's Persistent Store is automatically synchronized property).
with CloudKit servers and the data is available on every device logged in to
the same iCloud account. The NSPersistentCloudKitContainer class is a subclass Thanks to this amazing API, creating an application that stores information
of the NSPersistentContainer class and therefore it includes the same initializer. locally with Core Data and synchronizes the data with a CloudKit database
is extremely simple. All we have to do is to define the Core Data stack with
NSPersistentCloudKitContainer(name: String)—This initializer the NSPersistentCloudKitContainer class and then create the Core Data
creates a Persistent Store with the name specified by the name application as always. As an example, we are going to create an application
that stores countries and cities. We need two entities called Cities and
argument.
Countries. The Cities entity needs an attribute of type String called name
and a To-One relationship called country.
In addition to common properties, like the viewContext property to return a
reference to the context, this subclass also includes the following methods
Figure 15-15: Cities entity
in case our application needs to retrieve records manually.
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
There is one more requirement for the model to be ready to work with instead (see Listing 10-56).
CloudKit. We must select the Configuration (Figure 15-17, number 1) and
check the option Used with CloudKit in the Data Model Inspector panel Listing 15-20: Preparing Core Data to work with CloudKit
(Figure 15-17, number 2). This makes sure that if we create other 
configurations later, the system knows which one must be synchronized import SwiftUI
with CloudKit servers. import CoreData
Listing 15-22: Listing the countries stored in the Persistent Store As previous examples, the view includes a button to open the
InsertCountryView view to let the user insert a new country.

import SwiftUI
import CoreData Listing 15-23: Inserting new countries in the Persistent Store

struct ContentView: View {
import SwiftUI
@Environment(\.managedObjectContext) var dbContext
import CoreData
@FetchRequest(sortDescriptors: [SortDescriptor(\Countries.name, order: .forward)]) var
listCountries: FetchedResults<Countries>
@State private var openSheet: Bool = false struct InsertCountryView: View {
@Environment(\.managedObjectContext) var dbContext The following is the ShowsCitiesView view opened when a country is selected
@Environment(\.dismiss) var dismiss
@State private var inputName: String = "" by the user.
var body: some View { Listing 15-24: Listing the cities stored in the Persistent Store
VStack {
HStack { 
Text("Country:") import SwiftUI
TextField("Insert Country", text: $inputName) import CoreData
.textFieldStyle(.roundedBorder)
}
HStack { struct ShowCitiesView: View {
Spacer() @FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "FALSEPREDICATE"))
Button("Save") { var listCities: FetchedResults<Cities>
let text = inputName.trimmingCharacters(in: .whitespaces) @State private var openSheet: Bool = false
if !text.isEmpty { let selectedCountry: Countries?
let newCountry = Countries(context: dbContext)
newCountry.name = text init(selectedCountry: Countries?) {
do { self.selectedCountry = selectedCountry
try dbContext.save() if selectedCountry != nil {
} catch { _listCities = FetchRequest(sortDescriptors: [SortDescriptor(\Cities.name, order:
print("Error saving country") .forward)], predicate: NSPredicate(format: "country = %@", selectedCountry!), animation:
} .default)
dismiss() }
} }
} var body: some View {
} List {
Spacer()
ForEach(listCities) { city in
}.padding()
} Text(city.name ?? "Undefined")
} }
struct InsertCountryView_Previews: PreviewProvider { }
static var previews: some View { .navigationBarTitle(selectedCountry?.name ?? "Undefined")
InsertCountryView() .toolbar {
.environment(\.managedObjectContext, ApplicationData.preview.container.viewContext) ToolbarItem(placement: .navigationBarTrailing) {
} Button("Add City") {
} openSheet = true
 }
}
}
There is also nothing new in this view. We create a new Countries object .sheet(isPresented: $openSheet) {
InsertCityView(country: selectedCountry)
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 struct ShowCitiesView_Previews: PreviewProvider {
Countries object and uploads it to CloudKit servers. static var previews: some View {
NavigationStack {
ShowCitiesView(selectedCountry: nil) newCity.name = text
.environment(\.managedObjectContext, ApplicationData.preview.container.viewContext) newCity.country = country
} do {
} try dbContext.save()
} } catch {
 print("Error saving city")
}
dismiss()
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 Spacer()
predicate and then use the Countries object returned by the selectedCountry }.padding()
}
property to create a new FetchRequest structure with a predicate that filters }
the cities by country (see Listing 10-68). struct InsertCityView_Previews: PreviewProvider {
static var previews: some View {
The ShowCitiesView view also includes a button to let the user insert new InsertCityView(country: nil)
cities. The following is the view opened by the sheet() modifier when the .environment(\.managedObjectContext, ApplicationData.preview.container.viewContext)
}
button is pressed. }

Listing 15-25: Inserting new cities in the Persistent Store
 Again, we just create a new Cities object with the value inserted by the user
import SwiftUI when the Save button is pressed and the system takes care of creating the
import CoreData record an uploading it to CloudKit.
The application shows the list of countries and cities stored in the
struct InsertCityView: View {
@Environment(\.managedObjectContext) var dbContext Persistent Store, and allows the user to insert new values, as previous Core
@Environment(\.dismiss) var dismiss Data applications, but now all the information is uploaded to CloudKit
@State private var inputName: String = ""
let country: Countries? servers and automatically shared with other devices.
Custom Implementation

recordName argument is the name we want to give to the record, and value. The value must be any of the following types: NSString,
the zoneID argument is the identifier of the custom zone where we NSNumber, NSData, NSDate, NSArray, CLLocation, CKAsset, and Reference.
want to store it. object(forKey: String)—This method returns the value associated
recordName—This property returns a string with the name of the with the key specified by the forKey argument. The value is returned
record. as a generic CKRecordValue type that we must cast to the right data
zoneID—This property returns a CKRecordZone.ID object with the ID of type.
the zone the record belongs to.
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 When we want to access data stored in CloudKit, we must download the
a default zone, but the Private and Shared databases can include custom records from the database and read their values. Records may be fetched
zones. In the case of the Private database, the custom zones are optional from a database one by one using their ID or in a batch using a query. To
(although they are required for synchronization, as we will see later). define a query, the framework provides the CKQuery class. The class
Zones are like sections inside a database to separate records that are not includes the following initializer and properties.
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 CKQuery(recordType: String, predicate: NSPredicate)—This
list of Christmas gifts. In cases like this, we can create a zone to store the initializer creates a CKQuery object to fetch multiple records from a
records that include information about countries and cities and another database. The recordType argument specifies the type of records we
zone to store the records that include information about the gifts. The want to fetch, and the predicate argument determines the matching
CloudKit framework provides the CKRecordZone class to represent these criteria we want to use to select the records.
zones. The class includes an initializer to create custom zones and a type
method to get a reference to the zone by default. recordType—This property sets or returns a string that determines
the type of records we want to fetch.
CKRecordZone(zoneName: String)—This initializer creates a predicate—This property sets or returns an NSPredicate object that
object to represent a zone with the name specified by
CKRecordZone defines the matching criteria for the query.
the zoneName argument. sortDescriptors—This property sets or returns an array of
default()—This type method returns the CKRecordZone object that objects that determine the order of the records
NSSortDescriptor
represents the zone by default. returned by the query.
If we click on the CloudKit Database button, a panel is loaded to edit the you need to click on this button and select the Private Database
database. The panel includes an option at the top to select the container instead.
(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. As we already explained, the schema (the database model) is automatically
generated by CloudKit servers when we save records from our app during
Figure 15-21: Database panel development. For instance, if our app creates a Books record, the server
creates a record type called Books and adds it to the database model. In
theory, this is enough to create the model, but in practice we always need
to erase record types or values that we don't use anymore and add or
modify others that the app may need later. For this purpose, the
dashboard provides access to the schema on the left-side bar.
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
From the Record Types option, we can add, modify, or delete record types
our app (the one that is going to be available to our users).
(Entities) and their values (Attributes). The option to add a new record type
During development, we can store information in the database for testing. is at the top of the panel (Figure 15-23, number 1), and the record types
Below the configuration option is the Data section (Figure 15-21, number
already created are listed below (Figure 15-23, number 2).
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 Figure 15-23: Record types
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.
} }
func readCountries() async { 
let predicate = NSPredicate(format: "TRUEPREDICATE")
let query = CKQuery(recordType: "Countries", predicate: predicate)
We begin by defining two structures, Country and City, to store the name of
do { the country and city, and also a reference to the record downloaded from
let list = try await database.records(matching: query, inZoneWith: nil, desiredKeys: nil, the CloudKit database, and two more for our view model, CountryViewModel
resultsLimit: 0)
and CityViewModel. These view models identify each value by the record ID
await MainActor.run { (CKRecord.ID) and include a computed property to return a string with the
listCountries = [] name.
for (_, result) in list.matchResults {
if let record = try? result.get() { The observable object defines the @Published properties we need to store
let newCountry = Country(name: record["name"], record: record) the data locally and show it to the user. The listCountries property is an array
let newItem = CountryViewModel(id: record.recordID, country: newCountry)
listCountries.append(newItem) 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
}
listCountries.sort(by: { $0.countryName < $1.countryName })
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
} catch {
print("Error: \(error)")
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
}
func readCities(country: CKRecord.ID) async {
we only want the user to be able to share the data between his or her own
let predicate = NSPredicate(format: "country = %@", country) devices.)
let query = CKQuery(recordType: "Cities", predicate: predicate) The observable object also includes methods to add and read records. For
do {
instance, the insertCountry() and insertCity() methods are going to be called
let list = try await database.records(matching: query, inZoneWith: nil, desiredKeys: nil, from the views when the user inserts a new country or city. Their task is to
resultsLimit: 0) 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
await MainActor.run {
listCities = [] the countries, we use the string "idcountry" followed by a random value
for (_, result) in list.matchResults { generated by the UUID() function. Using this ID, we create a CKRecord object
if let record = try? result.get() {
let newCity = City(name: record["name"], record: record) of type Countries, then add a property called "name" with the value
let newItem = CityViewModel(id: record.recordID, city: newCity) received by the method, and finally save it in CloudKit servers with the
listCities.append(newItem)
} save() method of the CKDatabase object. This method generates an operation
} that communicates with the servers asynchronously. If there is no error, we
listCities.sort(by: { $0.cityName < $1.cityName })
} add the record to the listCountries property, which updates the views and the
} catch { screen.
print("Error: \(error)")
}
}
The method to add a city is the same, with the exceptions that we must the list of cities that belong to the selected country, and another view to
define the type of records as Cities and add an extra attribute to the record allow the user to insert a new city. The following is the initial view.
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 Listing 15-27: Listing the countries in the Private Database
method and an action of type deleteSelf, so when the record of the country is 
deleted, this record is deleted as well. struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
Next are the methods we need to implement to read the countries and @State private var openSheet: Bool = false
cities already stored in the database. In the readCountries() method, we
define a predicate with the TRUEPREDICATE keyword and a query for var body: some View {
NavigationStack {
records of type Countries. The record type asks the server to only look for
List {
records of type Countries, and the TRUEPREDICATE keyword determines ForEach(appData.listCountries) { country in
that the predicate will always return true, so we get back all the records NavigationLink(destination: ShowCitiesView(selectedCountry: country)) {
available. If the query doesn't return any errors, we get the records from Text(country.countryName)
}
the matchResults value and add them to the listCountries array to update the }
views. }
.navigationBarTitle("Countries")
The readCities() method is very similar, except that this time we are getting .toolbar {
the list of cities that belong to the country selected by the user. (The view ToolbarItem(placement: .navigationBarTrailing) {
Button("Add Country") {
that shows the cities only opens when the user taps on a row to select a openSheet = true
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. .sheet(isPresented: $openSheet) {
InsertCountryView()
}
IMPORTANT: This example assumes that you have assigned the }
}
bundle's name to the container and therefore we get a reference to }
the container with the default() type method. If the name assigned to struct ContentView_Previews: PreviewProvider {
static var previews: some View {
the container is different than the bundle's, you can specify it with ContentView().environmentObject(ApplicationData())
}
the CKContainer initializer, as in CKContainer(identifier: }
"iCloud.com.mydomain.MyContainer"). 
For the interface, we need a total of four views: a view to show the list of The countries stored in the CloudKit database are retrieved by the
countries, a view to allow the user to insert a new country, a view to show readCountries() method in our model. This method is called when the
observable object is initialized, so all the view has to do is to list the
countries stored in the listCountries property.
To let the user add a new country, the view includes a sheet() modifier that }
}
opens the InsertCountryView view. The following is our implementation of this 
view.
This view includes a TextField view to insert the name of the country and a
Listing 15-28: Storing countries in the Private database 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
import SwiftUI value inserted by the user and calls the save() method on the database to
import CloudKit
store it in CloudKit servers.
struct InsertCountryView: View {
Next is the view necessary to show the list of cities available for each
@EnvironmentObject var appData: ApplicationData country. The view is called ShowCitiesView and opens when the user taps on a
@Environment(\.dismiss) var dismiss
row in the initial view to select a country.
@State private var inputName: String = ""
@State private var buttonDisabled: Bool = false Listing 15-29: Listing the cities of a country

var body: some View { import SwiftUI
VStack { import CloudKit
HStack {
Text("Country:")
TextField("Insert Country", text: $inputName) struct ShowCitiesView: View {
.textFieldStyle(.roundedBorder) @EnvironmentObject var appData: ApplicationData
} @State private var openSheet: Bool = false
HStack { let selectedCountry: CountryViewModel
Spacer()
Button("Save") { var body: some View {
let text = inputName.trimmingCharacters(in: .whitespaces) VStack {
if !text.isEmpty { List {
buttonDisabled = true
ForEach(appData.listCities) { city in
Text(city.cityName)
Task(priority: .high) {
}
await appData.insertCountry(name: text)
}
dismiss()
}
}
.navigationBarTitle(selectedCountry.countryName)
}
.toolbar {
}.disabled(buttonDisabled)
ToolbarItem(placement: .navigationBarTrailing) {
}
Button("Add City") {
Spacer()
openSheet = true
}.padding()
}
}
}
}
}
struct InsertCountryView_Previews: PreviewProvider {
.sheet(isPresented: $openSheet) {
static var previews: some View {
InsertCityView(country: selectedCountry.id)
InsertCountryView().environmentObject(ApplicationData())
} .textFieldStyle(.roundedBorder)
.task { }
await appData.readCities(country: selectedCountry.id) HStack {
} Spacer()
} Button("Save") {
} let text = inputName.trimmingCharacters(in: .whitespaces)
struct ShowCitiesView_Previews: PreviewProvider { if !text.isEmpty {
static var previews: some View { buttonDisabled = true
ShowCitiesView(selectedCountry: CountryViewModel(id: CKRecord.ID(recordName: "Test"),
country: Country(name: "Test", record: CKRecord(recordType: "Cities", recordID:
Task(priority: .high) {
CKRecord.ID(recordName: "Test")))))
await appData.insertCity(name: text, country: self.country)
.environmentObject(ApplicationData())
dismiss()
}
}
}
}

}.disabled(buttonDisabled)
}
This view includes a property of type CountryViewModel called selectedCountry Spacer()
}.padding()
to receive the information about the selected country. Form this property, }
we get the country's record ID and call the readCities() method in the model }
struct InsertCityView_Previews: PreviewProvider {
when the view appears to retrieve the cities available for that country. The static var previews: some View {
view creates a list with these values and then includes a sheet() modifier to InsertCityView(country: CKRecord.ID(recordName: "Test"))
.environmentObject(ApplicationData())
let the user add more. The modifier opens a view called InsertCityView for }
this purpose. }

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.
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.
to the copy of the app that registered the subscription. in Chapter 14). The following is the method defined by the
Database subscriptions are created from the CKDatabaseSubscription class (a UIApplicationDelegate class for this purpose.
performed asynchronously, we cannot do it until they are finished. That's Because we are working with an app delegate, we need our model to be a
the reason why, in the example of Listing 15-35, we execute a method in singleton, so we can reference it from anywhere in our code (see
the model called checkUpdates() that takes a closure. This method downloads Singletons in Chapter 3). The following are the new properties we need to
the new information and executes the closure when finished. This way, we include in our model and the changes required by the initializer. (This
can call the completionHandler closure after all the operations have been example assumes that we are working with the model introduced in Listing
processed. 15-26.)
IMPORTANT: The UIApplicationDelegate class also defines an Listing 15-37: Defining the properties to control the subscription and the
asynchronous method to receive remote notifications called custom zone
application(UIApplication, didReceiveRemoteNotification: Dictionary). In this 
example, we are using concurrent operations, but if you need to class ApplicationData: ObservableObject {
@AppStorage("subscriptionSaved") var subscriptionSaved: Bool = false
perform asynchronous operations, you may implement this method @AppStorage("zoneCreated") var zoneCreated: Bool = false
@AppStorage("databaseToken") var databaseToken: Data = Data()
instead. @AppStorage("zoneToken") var zoneToken: Data = Data()
Of course, the protocol methods defined in Listing 15-35 are only called if @Published var listCountries: [CountryViewModel] = []
@Published var listCities: [CityViewModel] = []
we assigned the class as the app delegate with the var database: CKDatabase!
@UIApplicationDelegateAdaptor property wrapper from the App structure.
static let shared = ApplicationData()
Listing 15-36: Assigning the app's delegate
 private init() {
import SwiftUI let container = CKContainer.default()
database = container.privateCloudDatabase
implement closures that execute code after the operations are over, and CKDatabaseOperationclass called CKFetchDatabaseChangesOperation. This class
this is the approach we take in this example. includes the following initializer.
The procedure is as follows. Every time we call the checkUpdates() method to
download the data, we send a closure to the method with the code we CKFetchDatabaseChangesOperation(previousServerChangeT
want to execute once the operation is over. This way, we make sure that oken: CKServerChangeToken?)—This initializer creates an
the operations are over before doing anything else. operation to fetch changes from a database. The argument is a token
that determines which changes were already fetched. If we specify a
Listing 15-39: Initiating the process to get the updates from the server token, only the changes that occurred after the token was created are

fetched.
func checkUpdates(finishClosure: @escaping (UIBackgroundFetchResult) -> Void) {
Task(priority: .high) {
await configureDatabase() The class also includes properties to define completion handlers (closures)
downloadUpdates(finishClosure: finishClosure)
} for every step of the process.
}

recordZoneWithIDChangedBlock—This property sets a closure
The checkUpdates() method calls the configureDatabase() method again to make that is executed to report which zones present changes. The closure
sure that the database is configured properly. receives a value of type CKRecordZone.ID with the identifier of the zone
To simplify the code, we moved the statements to an additional method that changed.
called downloadUpdates(). So after we confirm that the zone was created, we changeTokenUpdatedBlock—This property sets a closure that is
call this method with a reference to the closure received by the executed to provide the last database token. The closure receives an
checkUpdates() method. (We pass the closure from one method to another so
object of type CKServerChangeToken with the current token that we can
we can execute it after all the operations are over, as we will see next.) store to send to subsequent operations.
IMPORTANT: Passing closures from one method to another is a way fetchDatabaseChangesResultBlock—This property sets a closure
to control the order in which the code is executed when we use that is executed when the operation is over. The closure receives a
concurrent operations. We chose this programming pattern for this Result enumeration to report the success or failure of the operation.
example because it simplifies the code, but as we mentioned before, The enumeration value includes a tuple and a CKError value to report
in some cases may be better to implement Swift concurrency. errors. The tuple includes two values: a CKServerChangeToken object
with the last token and a Boolean value that indicates if there are
Before implementing the downloadUpdates() method and process the changes more changes available.
in the database, we ought to study the operations provided by the CloudKit
framework for this purpose. The operation to fetch the list of changes After the completion of the CKFetchDatabaseChangesOperation operation, we
available in the database is created from a subclass of the must perform another operation to download the changes. For this
purpose, the framework includes the CKFetchRecordZoneChangesOperation class receives two values: a CKRecord.ID object with the identifier of the
with the following initializer. record that was deleted, and a string with the record's type.
recordZoneChangeTokensUpdatedBlock—This property sets a
CKFetchRecordZoneChangesOperation(recordZoneIDs:
closure that is executed when the change token for the zone is
[CKRecordZone.ID], configurationsByRecordZoneID:
updated. The closure receives three values: a CKRecordZone.ID with the
Dictionary)—This initializer creates an operation to download
identifier of the zone associated to the token, a CKServerChangeToken
changes from a database. The recordZoneIDs argument is an array
object with the current token, and a Data structure with the last token
with the IDs of all the zones that present changes, and the
sent by the app to the server.
configurationsByRecordZoneID argument is a dictionary with
configuration values for each zone. The dictionary takes
recordZoneFetchResultBlock—This property sets a closure that is
executed when the operation finishes downloading the changes of a
CKRecordZone.ID objects as keys and options determined by an object
zone. The closure receives two values: a CKRecordZone.ID with the
of the ZoneConfiguration class included in the
zone's identifier, and a Result enumeration value to report the success
CKFetchRecordZoneChangesOperation class. The class includes three
or failure of the operation. The enumeration includes two values: a
properties to define the options: desiredKeys (array of strings with the
tuple and a CKError value to report errors. In turn, the tuple includes
keys we want to retrieve), previousServerChangeToken (CKServerChangeToken
three values: a CKServerChangeToken object with the current token, a
object with the current token), and resultsLimit (integer that determines
Data structure with the last token sent to the server, and a Boolean
the number of records to retrieve).
value that indicates if there are more changes available.
The CKFetchRecordZoneChangesOperation class also includes properties to define fetchRecordZoneChangesResultBlock—This property sets a
completion handlers (closures) for every step of the process. closure that is executed after the operation is over. The closure
receives a Result enumeration value to report errors.
recordWasChangedBlock—This property sets a closure that is
executed when a new or updated record is downloaded. The closure CloudKit servers use tokens to know which changes were already sent to
receives two values: a CKRecord.ID with the identifier of the record every instance of the app, so the information is not downloaded twice
that changed, and a Result enumeration value to report the success or from the same device. If a device stores or modifies a record, the server
failure of the operation. The enumeration includes two values: a generates a new token, so the next time a device accesses the servers only
CKRecord object with the record that changed and a CKError value to
the changes introduced after the last token was created will be
downloaded.
report errors.
recordWithIDWasDeletedBlock—This property sets a closure that Figure 15-27: Tokens
is executed when the operation finds a deleted record. The closure
later the user decides to create a new record from Device 2 (Record 2), a operation.fetchDatabaseChangesResultBlock = { result in
guard let values = try? result.get() else {
new token will be created (B). The next time Device 1 connects to the finishClosure(UIBackgroundFetchResult.failed)
server, it will find that its token is different from the server's token, so it return
}
will download the modifications inserted after token A. if zonesIDs.isEmpty {
Tokens are great because they allow us to only get the latest changes, but finishClosure(UIBackgroundFetchResult.noData)
} else {
this process is not automatic, we are responsible of storing the current changeToken = values.serverChangeToken
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, let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration()
configuration.previousServerChangeToken = changeZoneToken
we need two tokens: one to keep track of the changes in the database and
let fetchOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zonesIDs,
another for the custom zone created by the configureDatabase() method. To configurationsByRecordZoneID: [zonesIDs[0]: configuration])
work with these values, we are going to use two variables called
changeToken, for the database token, and fetchChangeToken, for the token of fetchOperation.recordWasChangedBlock = { recordID, result in
our custom zone, and we are going to store them permanently with the guard let record = try? result.get() else {
print("Error")
@AppStorage properties defined before in the model (databaseToken and return
zoneToken). All this process is performed by the downloadUpdates() method. }
if record.recordType == "Countries" {
Task(priority: .high) {
Listing 15-40: Downloading the updates from the server let index = self.listCountries.firstIndex(where: { item in
return item.id == record.recordID
 })
func downloadUpdates(finishClosure: @escaping (UIBackgroundFetchResult) -> Void) { await MainActor.run {
let newCountry = Country(name: record["name"], record: record) if let data = try? NSKeyedArchiver.archivedData(withRootObject: changeToken!,
let newItem = CountryViewModel(id: record.recordID, country: newCountry) requiringSecureCoding: false) {
if index != nil { Task(priority: .high) {
self.listCountries[index!] = newItem await MainActor.run {
} else { self.databaseToken = data
self.listCountries.append(newItem) }
} }
self.listCountries.sort(by: { $0.countryName < $1.countryName }) }
} }
} if changeZoneToken != nil {
} if let data = try? NSKeyedArchiver.archivedData(withRootObject: changeZoneToken!,
} requiringSecureCoding: false) {
fetchOperation.recordWithIDWasDeletedBlock = { recordID, recordType in Task(priority: .high) {
await MainActor.run {
if recordType == "Countries" { self.zoneToken = data
Task(priority: .high) { }
let index = self.listCountries.firstIndex(where: {(item) in }
return item.id == recordID }
}) }
await MainActor.run {
finishClosure(UIBackgroundFetchResult.newData)
if index != nil {
}
self.listCountries.remove(at: index!)
self.database.add(fetchOperation)
} }
self.listCountries.sort(by: { $0.countryName < $1.countryName }) }
} database.add(operation)
} }
}

}
fetchOperation.recordZoneChangeTokensUpdatedBlock = { zoneID, token, data in
changeZoneToken = token
This is a very long method that we need to study piece by piece. As
} mentioned before, we start by defining the properties we are going to use
fetchOperation.recordZoneFetchResultBlock = { zoneID, result in to store the tokens (one for the database and another for the custom
guard let values = try? result.get() else { zone). Next, we check if there are tokens already stored in the @AppStorage
print("Error")
return properties. Because the tokens are instances of the CKServerChangeToken
} class, we cannot store their values directly in App Storage, we must first
changeZoneToken = values.serverChangeToken
} convert them into Data structures. This is the reason why, when we read
fetchOperation.fetchRecordZoneChangesResultBlock = { result in the values, we cast them as Data with the as? operator and then unarchive
switch result { them with the unarchivedObject() method of the NSKeyedUnarchiver class (see
case .failure(_):
finishClosure(UIBackgroundFetchResult.failed) Archiving in Chapter 10).
return Next, we configure the operations necessary to get the updates from the
default:
break
server. We must perform two operations on the database, one to
} download the list of changes available and another to download the actual
if changeToken != nil {
changes and show them to the user. The operations are performed and The CKFetchRecordZoneChangesOperation operation is performed over the zones
then the results are reported to the closures assigned to their properties. that changed, so we must initialize it with the array of zone identifiers
The first operation we need to perform is the CKFetchDatabaseChangesOperation generated by the previous operation. The initializer also requires a
operation. The initializer requires the previous token to get only the dictionary with the zone identifiers as keys and ZoneConfiguration objects that
changes that are not available on the device, so we pass the value of the include the previous token for each zone as values. Because in this example
changeToken property. Next, we define the closures for each of its properties. we only work with one zone, we read the first element of the zonesIDs array
This operation includes three properties, one to report the zones that to get the identifier of our custom zone and provide a ZoneConfiguration
changed, one to report the creation of a new database token, and another object with the current token for the zone stored in the changeZoneToken
to report the conclusion of the operation. The first property defined in our variable.
example is recordZoneWithIDChangedBlock. The closure assigned to this This operation works like the previous one. The changes are fetched, and
property is executed every time the system finds a zone whose content has the results are reported to the closures assigned to its properties. The first
changed. In this closure, we add the zone ID to an array to keep a reference property declared in Listing 15-40 is recordWasChangedBlock. The closure
of each zone that changed. assigned to this property is called every time a new or updated record is
Something similar happens with the closure assigned next to the received. Here, we check if the record is of type Countries and store it in
changeTokenUpdatedBlock property. This closure is executed every time the the corresponding array. When the record is of type Countries, we use the
system decides to perform the operation again to download the changes in firstIndex(where:) method to look for duplicates. If the record already exists in
separate processes. To make sure that we only receive the changes that we the array, we update its values, otherwise, we add the record to the list.
did not process yet, we use this closure to update the changeToken property The closure of the recordWithIDWasDeletedBlock property defined next is
with the current token. executed every time the app receives the ID of a deleted record (a record
The last property we have defined for this operation is that was deleted from the CloudKit database). In this case, we do the same
fetchDatabaseChangesResultBlock. The closure assigned to this property is as before but instead of updating or adding the record we remove it from
executed to let the app know that the operation is over, and this is how we the list with the remove() method.
know that we have all the information we need to begin downloading the The closures assigned to the next two properties,
changes with the second operation. This closure receives a Result recordZoneChangeTokensUpdatedBlock and recordZoneFetchResultBlock, are executed
enumeration value, which includes a tuple with two values and an Error when the process completes a cycle, either because the system decides to
value to report errors. If no values are returned, we execute the finishClosure download the data in multiple processes, or the operation finished fetching
closure with the value failed and the operation is over. On the other hand, if the changes in a zone. Depending on the characteristics of our application,
there are values available, we check if the zoneIDs array contains any zone we may need to perform some tasks in these closures, but in our example,
ID. If it is empty, it means that there are no changes available and therefore we just store the current token in the changeZoneToken variable so the next
we execute the finishClosure closure with the value noData, but if the array is time the operation is performed we only get the changes we have not
not empty, we store the last token in the changeToken variable and configure downloaded yet.
the CKFetchRecordZoneChangesOperation operation to download the changes. Finally, the closure assigned to the fetchRecordZoneChangesResultBlock property
is executed to report that the operation is over. The closure receives a Result
value to report errors. If there is an error, we call the finishClosure closure }
}
with the value failed to tell the system that the operation failed, otherwise, func insertCity(name: String, country: CKRecord.ID) async {
we store the current tokens in the @AppStorage properties and call the await configureDatabase()
finishClosure closure with the value newData, to tell the system that new data
let text = name.trimmingCharacters(in: .whitespaces)
has been downloaded. Notice that to store the tokens we must turn them if !text.isEmpty {
into Data structures and encode them with the archivedData() method of the let zone = CKRecordZone(zoneName: "listPlaces")
let id = CKRecord.ID(recordName: "idcity-\(UUID())", zoneID: zone.zoneID)
NSKeyedArchiver class (see Archiving in Chapter 10). let record = CKRecord(recordType: "Cities", recordID: id)
Lastly, after the definition of each operation and their properties, we call record.setObject(text as NSString, forKey: "name")
the add() method of the CKDatabase object to add them to the database.
let reference = CKRecord.Reference(recordID: country, action: .deleteSelf)
There is one more change we must perform in our model for the record.setObject(reference, forKey: "country")
subscription to work. So far, we have stored the records in the zone by
default, but as we already mentioned, subscriptions require the records to let bundle = Bundle.main
if let fileURL = bundle.url(forResource: "Toronto", withExtension: "jpg") {
be stored in a custom zone. The following are the changes we must let asset = CKAsset(fileURL: fileURL)
introduce to the insertCountry() and insertCity() methods to store the records record.setObject(asset, forKey: "picture")
}
inside the listPlaces zone created before. do {
try await database.save(record)
Listing 15-41: Storing the records in a custom zone await MainActor.run {
let newCity = City(name: record["name"], record: record)
 let newItem = CityViewModel(id: record.recordID, city: newCity)
func insertCountry(name: String) async { listCities.append(newItem)
await configureDatabase() listCities.sort(by: { $0.cityName < $1.cityName })
}
} catch {
let text = name.trimmingCharacters(in: .whitespaces) print("Error: \(error)")
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 {
All we have to do to store a record in a custom zone is to create the
try await database.save(record) CKRecordZone object and assign its ID to the record ID by including it in the
await MainActor.run { initializer of the CKRecord.ID object.
let newCountry = Country(name: record["name"], record: record)
let newItem = CountryViewModel(id: record.recordID, country: newCountry) Notice that the first thing we do in both methods is to call the
listCountries.append(newItem) configureDatabase() method. We call this method again, so every time a record
listCountries.sort(by: { $0.countryName < $1.countryName })
} is inserted, we check that the subscription and the zone were already
} catch { added to the database.
print("Error: \(error)")
}
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.
Listing 15-42: Checking CloudKit availability Do It Yourself: Update the insertCountry() method in the ApplicationData
 class with the code in Listing 15-42. Run the application on a device
func insertCountry(name: String) async { and activate Airplane Mode from Settings. Add a new country. You
await configureDatabase()
should see a CKError on the console that reads "Network
do { Unavailable".
let container = CKContainer.default()
let status = try await container.accountStatus()
if status != CKAccountStatus.available { In the last example, we just checked whether an error occurred or not and
print("iCloud Not Available")
return
proceeded accordingly, but we can also identify the type of error returned
} by the operation. Errors are structures that conform to the Error protocol.
} catch {
print("Error: \(error)")
Every time we want to read an error, we must cast it to the right type. In
return CloudKit, the errors are of type CKError, a structure that includes the
}
let text = name.trimmingCharacters(in: .whitespaces)
following property to return the error code.
if !text.isEmpty {
let zone = CKRecordZone(zoneName: "listPlaces")
let id = CKRecord.ID(recordName: "idcountry-\(UUID())", zoneID: zone.zoneID)
code—This property returns a value that identifies the error. The
let record = CKRecord(recordType: "Countries", recordID: id) property is of type CKError.Code; an enumeration defined by the
record.setObject(text as NSString, forKey: "name")
CKError structure with values that represent all the errors produced by
Deploy to Production
Listing 15-43: Checking for errors


func checkZones() async {
let newZone = CKRecordZone(zoneName: "myNewZone") In CloudKit's dashboard, at the bottom of the panel on the left, there is a
do { list of options to work with the database schema. We can export the
try await database.recordZone(for: newZone.zoneID)
} catch { schema, import a schema from our computer, reset the schema to start
if let error = error as? CKError { from scratch, and deploy the schema to production. This last option is the
if error.code == CKError.Code.zoneNotFound {
print("Not found") one we need to select when we want to prepare our app for distribution
} else { (to be sold in the App Store).
print("Zone Found")
} The Deploy Schema Changes option opens a panel where we can see the
} features that are going to be transferred to the Production environment.
}
} This includes record types and indexes, but it does not include records
 (values added for testing). If we agree, we must press the Deploy button to
finish the process, and our database in CloudKit will be ready for
Do It Yourself: Add the checkZones() method in Listing 15-43 to the distribution.
ApplicationData class. Call this method from the initializer (checkZones()).
Run the application again. You should see the message "Not Found" IMPORTANT: The Production environment is used by apps that are
on the console because we are trying to access a zone with a submitted to Apple for distribution. This step is required for your
different name than the one we have created before. application to be published in the App Store. If you don't deploy the
changes to production, the database is not going to be available to
your users. To learn how to submit your app to the App Store, read
Chapter 20.
16.1 Integration with UIKit
CHAPTER 16 - FRAMEWORK INTEGRATION 
SwiftUI is a new framework and therefore not everything we need to build
a professional application is available. For mobile applications, this means
that sometimes we must resort to the tools provided by the UIKit
framework.
We have introduced UIKit before. This is the framework SwiftUI
implements in the background to build most of the views and controls.
Some UIKit classes are used to run the application (UIApplication), to load
images (UIImage), to manage the device (UIDevice), the window (UIWindow),
and some to define the delegates used to set up the application and the
Scenes (UIApplicationDelegate and UIWindowSceneDelegate). And, of course, the
framework provides all the tools we need to create the interface, including
two basic classes to create and manage the views: UIView and
UIViewController.
These last two classes, UIView and UIViewController, are the ones we need to
implement if we want to add UIKit features to our SwiftUI interface.
Subclasses of the UIView class are used to present information on the
screen, such as labels and images, and to create controls, such as buttons,
sliders and switches. On the other hand, subclasses of the UIViewController
class are designed to present the views and include the functionality
necessary to process their values and interact with the user. To integrate
these tools into the SwiftUI interface, the SwiftUI framework defines two
protocols: UIViewRepresentable and UIViewControllerRepresentable.
Representable View makeUIView() and updateUIView() methods are mandatory. In the makeUIView()
method, we must create the instance of the UIKit view and return it, and

the updateUIView() method is used to update the view with values coming
from the SwiftUI interface.
The UIViewRepresentable protocol defines a structure that acts as a wrapper
The following example creates a UIKit view with a blue background and
for objects created from the UIView class or its subclasses. A structure that
includes it in a SwiftUI interface. We only need the makeUIView() method to
conforms to this protocol can present a UIKit view within a SwiftUI
create the UIKit view, but we also have to implement the updateUIView()
interface. To create and manage the UIKit view, the structure must
method because it is required by the protocol.
implement the following methods.
Listing 16-1: Preparing a UIKit view to work with SwiftUI
makeUIView(context: Context)—This method creates the UIKit 
view and returns it. The context argument is a reference to a structure import SwiftUI
of type UIViewRepresentableContext that provides information about the
state of the view. struct MyCustomView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
updateUIView(UIViewType, context: Context)—This method let view = UIView()
updates the UIKit view with information provided by the SwiftUI view.backgroundColor = UIColor(.blue)
return view
interface through a Binding property. The first argument is a reference }
to the UIKit view, and the context argument is a reference to a func updateUIView(_ uiView: UIViewType, context: Context) {
}
structure of type UIViewRepresentableContext that provides information }

about the state of the view.
dismantleUIView(UIViewType, coordinator: Coordinator)— The makeUIView() method is called every time a new instance of the
This type method prepares the view to be dismissed. The first MyCustomView structure is created. In this method, we create the UIView
argument is a reference to the UIKit view, and the coordinator view, give it a blue background, and return it. Therefore, every time we
argument is the object that sends values back to the SwiftUI interface. create an instance of the MyCustomView structure, a UIView is created and
makeCoordinator()—This method creates the object that included in our SwiftUI interface, as in the following example.
communicates information from the UIKit view back to the SwiftUI
Listing 16-2: Showing a UIKit view within a SwiftUI view
interface.

struct ContentView: View {
To include a UIView object in a SwiftUI interface (or an object created from var body: some View {
any of its subclasses), we must define a structure that conforms to the VStack {
MyCustomView()
UIViewRepresentable protocol and implement the methods listed above. The .frame(width: 200, height: 150)
.padding() and implements its methods. The following example illustrates how to
Spacer()
} create a UIViewRepresentable structure to work with this class.
}
}
 Listing 16-3: Sending and receiving values from the SwiftUI view

A representable view has a flexible size by default, but we can use SwiftUI import SwiftUI
modifiers to change it. In this example, we use the frame() modifier to assign
struct TextView: UIViewRepresentable {
a fixed width and height. @Binding var input: String
Figure 16-1: UIKit view in a SwiftUI interface func makeUIView(context: Context) -> UITextView {
let view = UITextView()
view.backgroundColor = UIColor.yellow
view.font = UIFont.systemFont(ofSize: 17)
view.delegate = context.coordinator
return view
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = input
}
 func makeCoordinator() -> CoordinatorTextView {
return CoordinatorTextView(input: $input)
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
class CoordinatorTextView: NSObject, UITextViewDelegate {
values from the SwiftUI view to the UIKit view, we use the updateUIView()
@Binding var inputCoordinator: String
method, but if we want to send values from the UIKit view to the SwiftUI
interface, we must implement the makeCoordinator() method. From this
init(input: Binding<String>) {
method, we must create an instance of a coordinator object and return it. self._inputCoordinator = input
A coordinator is an object that can send information back from the UIKit }
func textViewDidChange(_ textView: UITextView) {
view to the SwiftUI interface, usually by modifying Binding properties. How inputCoordinator = textView.text
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 This example creates a UIViewRepresentable structure called TextView. The
inserted by the user in a UITextView view and process it in SwiftUI, we must structure includes a Binding property called input to receive and pass the
create a coordinator class that conforms to the UITextViewDelegate protocol values to the SwiftUI view.
Below the definition of the TextView structure we define a class called property, so the property is connected to the input property in the
CoordinatorTextView. This is our view coordinator and its job is to send values representable view and we can pass values back and forth.
back to the SwiftUI view. For this purpose, we initialize it with a Binding When the user types or removes a character from the text view, the
property that is going to be associated with the input property defined by representable view calls the textViewDidChange() method in the coordinator
the TextView structure, and then implement a delegate method that is and the method assigns the current value in the text view to the
executed when a character is inserted or removed by the user. The method inputCoordinator property, and hence to the input property. This means that
receives the current text in the input field, so we assign it to the Binding the value is now available in the view's @State property and the Text view
property to send it back to the SwiftUI view. can show it on the screen.
The CoordinatorTextView object is created from the makeCoordinator() method On the other hand, when the user presses the Clear button, we assign an
when the UIViewRepresentable structure is initialized, so the structure is ready empty string to the @State property, the system executes the updateUIView()
from the beginning to receive and send values. method in the representable view, and the value of the Binding property
The view is implemented in the SwiftUI interface as before. All we need to connected to the @State property is assigned to the view, so the text view is
add now is a @State property to store and pass the value to the input cleared.
property.
Figure 16-2: UITextView view in a SwiftUI interface
Listing 16-4: Receiving and sending values to a UIKit view

struct ContentView: View {
@State private var inputText: String = "Initial text"
A UIKit view controller is created from a subclass of the UIViewController Listing 16-6: Creating the representable view for a UIKit view controller
class. The file is created from the File menu, as any other, but the option 
we need to select to get a subclass of a UIKit class is called Cocoa Touch import SwiftUI
Class. Once this option is selected, Xcode shows a window where we can
struct MyViewController: UIViewControllerRepresentable {
insert the name of the file and the class from which we want to create our
func makeUIViewController(context: Context) -> DetailViewController {
subclass. For our example, we have created a class called DetailViewController let controller = DetailViewController()
that inherits from the UIViewController class. return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
Listing 16-5: Creating a UIKit view controller }
}
 
import UIKit
The UIViewControllerRepresentable protocol works like the UIViewRepresentable
class DetailViewController: UIViewController { protocol. We define a structure that conforms to it and then include the
override func viewDidLoad() {
super.viewDidLoad()
methods we need to create and update the view. In our example, we only
let label = UILabel() define the makeUIViewController() method because all we need is to create an
label.frame = CGRect(x: 20, y: 16, width: 250, height: 30) instance of our view controller to present it on the screen. The following
label.font = UIFont.systemFont(ofSize: 30)
label.text = "Hello World!" SwiftUI view loads this view controller inside a NavigationStack view when the
view.addSubview(label) user presses a button.
}
}
 Listing 16-7: Loading a UIKit view controller from a SwiftUI view

When the view controller class is instantiated, it creates a view to struct ContentView: View {
represent the interface, assigns it to the view property, and calls the var body: some View {
NavigationStack {
viewDidLoad() method to tell our code that the view is ready. In this method, VStack {
we can perform all the initialization tasks we need. In our example, we NavigationLink("Open UIKit View", destination: {
MyViewController()
create a UILabel object to display a text on the screen. This object works like }).buttonStyle(.borderedProminent)
the SwiftUI Text view, but it requires some configuration. In our example, Spacer()
}.padding()
we give it a position and size by assigning a CGRect value to the frame }
property, define a font with a size of 30 pixels, give it the text to display, }
}
and then add it to the view controller's view with the addSubview() method.

Now that we have the view controller, we need to create a representable
view to turn it into a SwiftUI view, as in the following example.
This view includes a NavigationLink view that loads an instance of the
MyViewController structure, so we can navigate from the initial view to the
view controller created by this structure, as we do with normal SwiftUI
CHAPTER 17 - WEB
views.
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.
Apps can allow the user to access the web, but there are different ways to A link is a text or an image associated with a URL that indicates the location
do it. We can provide links for the user to open a document in the browser, of a document. When the user clicks or taps the link, the document is
embed a predefined browser into the app's interface, or load data in the opened. Links were designed for the web, but we can add them to our
background, process it, and show the result to the user. applications and let the system decide where to open the document (a
browser or another app). SwiftUI includes the Link view to create them.
the full URL before trying to open it. To read, create, or modify URL Listing 17-3: Encoding custom URLs
components, the Foundation framework defines the URLComponents 
structure. The structure includes the following initializer. struct ContentView: View {
@Environment(\.openURL) var openURL
@State private var searchURL = ""
URLComponents(string: String)—This initializer creates a
URLComponents structure with the components from the URL specified var body: some View {
VStack {
by the string argument. TextField("Insert URL", text: $searchURL)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
The structure includes several properties to read and modify the .disableAutocorrection(true)
components. The following are the most frequently used. Button("Open Web") {
if !searchURL.isEmpty {
var components = URLComponents(string: searchURL)
scheme—This property sets or returns the URL's protocol (e.g., components?.scheme = "https"
if let newURL = components?.string {
"http"). if let url = newURL.addingPercentEncoding(withAllowedCharacters:
.urlQueryAllowed) {
host—This property sets or returns the URL's domain (e.g., openURL(URL(string: url)!)
"www.google.com"). }
}
path—This property sets or returns the URL's components after the }
}.buttonStyle(.borderedProminent)
domain (e.g., "/index.php"). Spacer()
}.padding()
query—This property sets or returns the URL's parameters (e.g., }
"id=22"). }

queryItems—This property sets or returns an array of URLQueryItem
structures containing each of the parameters included in the URL. The URLComponents structure takes a string with the URL, extracts the
components, and assigns them to the structure's properties, so we can
The URLComponents structure also includes the following property to return read or modify them. In this example, we assign the "https" string to the
a string with the URL created from the components. 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
string—This property returns a string with the URL built from the property, replace invalid characters with percent-encoded characters, and
values of the components. open it.
In the following example, we allow the user to insert a URL, but we assign Figure 17-2: Custom URLs
the https protocol to the URL to make sure it is always included.
Safari View Controller

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.
The code in Listing 17-6 also modifies the dismissButtonStyle property to struct SafariBrowser: UIViewControllerRepresentable {
change the type of button displayed by the browser. Instead of Done, the @Binding var searchURL: URL
button now says Close. func makeUIViewController(context: Context) -> SFSafariViewController {
let config = SFSafariViewController.Configuration()
Figure 17-4: Custom Safari view controller config.barCollapsingEnabled = false
let safari = SFSafariViewController(url: searchURL, configuration: config)
return safari
}
func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {}
}

safariViewController(SFSafariViewController, }
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
didCompleteInitialLoad: Bool)—This method is called by the disableCoordinator = true
}
controller when the initial website finishes loading. }
safariViewControllerDidFinish(SFSafariViewController)—This 
that represents a web page in a navigation list. about the request, and the decisionHandler argument is a closure
reload()—This method reloads the current page (it refreshes the web that we must execute to report our decision. The closure takes a value
page). of type WKNavigationActionPolicy, an enumeration with the properties
cancel and allow.
stopLoading()—This method asks the view to stop loading the
content. webView(WKWebView, didStartProvisionalNavigation:
WKNavigation!)—This method is called on the delegate when the
To load a website, we must create a request. The UIKit framework offers view begins loading new content.
the URLRequest structure for this purpose. The structure includes the webView(WKWebView, didFinish: WKNavigation!)—This
following initializer. method is called on the delegate when the view finishes loading the
content.
URLRequest(url: URL, cachePolicy: CachePolicy,
webView(WKWebView, didFailProvisionalNavigation:
timeoutInterval: TimeInterval)—This initializer creates a request
WKNavigation!, withError: Error)—This method is called on the
to load the URL specified by the url argument. The cachePolicy
delegate when an error occurs loading the content.
argument is an enumeration that determines how the request will
work with the cache. The possible values are: useProtocolCachePolicy webView(WKWebView,
(default), reloadIgnoringLocalCacheData, didReceiveServerRedirectForProvisionalNavigation:
reloadIgnoringLocalAndRemoteCacheData, returnCacheDataElseLoad, WKNavigation!)—This method is called on the delegate when the
returnCacheDataDontLoad, and reloadRevalidatingCacheData. The server redirects the navigator to a different destination.
timeoutInterval argument is the maximum time allowed for the
system to process the request (60.0 by default). Only the first The WebKit view is a UIKit view and therefore we must use the
UIViewRepresentable protocol to create it. Once the representable view is
argument is required, the rest of the arguments are defined with
defined, the process to load a website in a WebKit view is simple; we
values by default.
provide the URL, create a request, and ask the view to load it.
A WebKit view can report the state of the content through a delegate. For
Listing 17-10: Loading a website with a WebKit View
this purpose, the framework defines the WKNavigationDelegate protocol. The

following are some of the methods included in this protocol.
import SwiftUI
import WebKit
webView(WKWebView, decidePolicyFor:
struct WebView: UIViewRepresentable {
WKNavigationAction, decisionHandler: Closure)—This method let searchURL: URL
is called on the delegate to determine if the view should process a
func makeUIView(context: Context) -> WKWebView {
request. The decidePolicyFor argument is an object with information let view = WKWebView()
let request = URLRequest(url: searchURL)
view.load(request)
return view With a WKWebView view, we can load any website we want, including those
} specified by the user. We just need to provide a way for the user to insert a
func updateUIView(_ uiView: WKWebView, context: Context) {}
URL, as we did in previous examples, and then execute the load() method
}
 again to load it. For this purpose, the following view includes a TextField
view and a button. When the button is pressed, we call a method in the
This example prepares the request with the URL received from the SwiftUI WebView structure to update the view with the URL inserted by the user.
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 Listing 17-12: Allowing the user the insert a URL
the WebView instance. 
import SwiftUI
Listing 17-11: Showing the WebKit view
 class ContentData: ObservableObject {
@Published var inputURL: String = ""
struct ContentView: View { }
var body: some View { struct ContentView: View {
WebView(searchURL: URL(string: "https://www.google.com")!) @ObservedObject var contentData = ContentData()
} var webView: WebView!
}
 init() {
webView = WebView(inputURL: $contentData.inputURL)
}
Do It Yourself: Create a Multiplatform project. Create a Swift file var body: some View {
called WebView.swift for the code in Listing 17-10. Update the VStack {
HStack {
ContentView view with the code in Listing 17-11. Run the application TextField("Insert URL", text: $contentData.inputURL)
on the iPhone simulator. You should see Google's website on the .autocapitalization(.none)
.disableAutocorrection(true)
screen. Button("Load") {
let text = contentData.inputURL.trimmingCharacters(in: .whitespaces)
if !text.isEmpty {
IMPORTANT: In the example of Listing 17-11, we open a secure URL webView.loadWeb(web: text)
}
(a URL that begins with the prefix https://) because these are the }
URLs allowed by default. As we have seen in Chapter 9, Apple }.padding(5)
webView
implements a system called App Transport Security (ATS) to block }
insecure URLs. If you want to allow users to load insecure URLs with }
}
a WKWebView view, you must configure the ATS system with the Allow 
Arbitrary Loads option (see Figure 9-3).
properties to the WebView structure and modify their values from the return CoordinatorWebView(input: $inputURL, back: $backDisabled, forward:
$forwardDisabled)
coordinator every time a document is loaded. }
}
class CoordinatorWebView: NSObject, WKNavigationDelegate {
Listing 17-15: Navigating back and forth in the navigation history
@Binding var inputURL: String
 @Binding var backDisabled: Bool
import SwiftUI @Binding var forwardDisabled: Bool
import WebKit
init(input: Binding<String>, back: Binding<Bool>, forward: Binding<Bool>) {
struct WebView: UIViewRepresentable { self._inputURL = input
@Binding var inputURL: String self._backDisabled = back
@Binding var backDisabled: Bool self._forwardDisabled = forward
@Binding var forwardDisabled: Bool }
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
let view: WKWebView = WKWebView() if let webURL = webView.url {
inputURL = webURL.absoluteString
func makeUIView(context: Context) -> WKWebView { backDisabled = !webView.canGoBack
view.navigationDelegate = context.coordinator forwardDisabled = !webView.canGoForward
let request = URLRequest(url: URL(string: "https://www.google.com")!) }
self.view.load(request) }
return view }
} 
func updateUIView(_ uiView: WKWebView, context: Context) {}
The session sets up the connection, but it does not perform any tasks. To upload(for: URLRequest, fromFile: URL, delegate:
download or upload data we must implement the following methods URLSessionTaskDelegate?)—This asynchronous method adds a
defined in the URLSession class. task to the session to upload the file in the URL indicated by the
fromFile argument. The delegate argument is the delegate object
data(from: URL, delegate: URLSessionTaskDelegate?)—This used by the task to report updates during the process. The method
asynchronous method adds a task to the session to download the returns a tuple with two values: a Data structure with the data
data at the URL indicated by the from argument. The delegate returned by the server and a URLResponse object with the status of the
argument is the delegate object used by the task to report updates request.
during the process. The method returns a tuple with two values: a
Data structure with the data returned by the server and a URLResponse These methods are asynchronous. When the data finishes downloading or
object with the status of the request. uploading, they return the result. For example, if we use the data() method
download(from: URL, delegate: URLSessionTaskDelegate?)— to get data from a website, the value returned includes a value with the
This asynchronous method adds a task to the session to download the data and an object of type URLResponse with the status of the request.
file at the URL indicated by the from argument. The delegate When we access a URL using the HTTP protocol, the response is
represented by an object of type HTTPURLResponse (a subclass of
argument is the delegate object used by the task to report updates
URLResponse). This class includes the statusCode property to return a code that
during the process. The method returns a tuple with two values: a
determines the status of the request. There are several codes available to
URL structure that indicates the location of the downloaded file and a
report things like the success of the request (200) or more drastic
URLResponse object with the status of the request.
situations like when the website has been moved to a different address
(301). If all we want is to make sure that the data was downloaded
The following are the methods defined by the class to upload data and correctly, we can check if the value of the statusCode property is equal to 200
files. before processing anything. The following example shows how to perform
a basic request.
upload(for: URLRequest, from: Data, delegate:
URLSessionTaskDelegate?)—This asynchronous method adds a Listing 17-16: Loading a remote document
task to the session to upload the data indicated by the from 
argument. The delegate argument is the delegate object used by the import SwiftUI
task to report updates during the process. The method returns a tuple
class ApplicationData: ObservableObject {
with two values: a Data structure with the data returned by the server @Published var webContent: String = ""
and a URLResponse object with the status of the request. @Published var buttonDisabled: Bool = false
These days, personal devices are mainly used to process images, videos, SwiftUI includes the PhotosPicker structure to generate a view that allows the
and sound, and Apple devices are no exception. SwiftUI can display an user to select one or multiple pictures from the Photo Library. The
image with an Image view, but it requires the assistance of other following is the view's initializer.
frameworks to process the image, present a video on the screen, or play
sounds. In this chapter, we introduce some of the tools provided by Apple PhotosPicker(selection: Binding, maxSelectionCount: Int?,
for this purpose. selectionBehavior: PhotosPickerSelectionBehavior, matching:
PHPickerFilter?, preferredItemEncoding:
EncodingDisambiguationPolicy, photoLibrary: PHPhotoLibrary,
label: Closure)—This initializer creates a PhotosPicker view with the
configuration specified by the arguments. The selection argument is
the Binding property that stores the references to the selected items.
The maxSelectionCount argument is the maximum number of items
we want to user to be able to select. The selectionBehavior argument
determines if the selection is going to be numbered or not. It is a
structure with the type properties default and ordered. The matching
argument determines the type of items the view should include. It is a
structure with the type properties bursts, cinematicVideos, depthEffectPhotos,
images, livePhotos, panoramas, screenRecordings, screenshots, slomoVideos,
timelapseVideos, and videos. The preferredItemEncoding argument
determines the encoding to use to process the items. It is a structure
with the type properties automatic (default), current, and compatible. The
photoLibrary argument provides access to the library. It is a structure
with the type method shared(). And the label argument is a closure that
provides the label for the button generated by the view.
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 PhotosPicker(selection: $selected, matching: .images, photoLibrary: .shared()) {
Text("Select a photo") }
and method to access the media. }
}
.onChange(of: selected) { item in
itemIdentifier—This property returns a string with the item's Task(priority: .background) {
identifier. if let data = try? await item?.loadTransferable(type: Data.self) {
picture = UIImage(data: data)
loadTransferable(type: Type)—This asynchronous method loads }
}
the item and assigns it to an instance of the data type specified by the }
type argument. The data type assigned to this argument must }
}
conform to the Transferable protocol. }

The PhotosPicker structure and all the data types required to read and
Most of the arguments in the PhotosPicker initializer are optional. For this
retrieve items from the library are defined in the PhotosUI framework, that
example, we only need to tell the picker where to store the references to
we must import along with SwiftUI. Another requirement of the structure
the selected items, the type of items we want to show to the user (images),
is a state property to store the selected items. If we want to allow the user
and where to get them (the shared library). The PhotosPicker structure
to select multiple items, the property must store an array of PhotosPickerItem
creates a button that opens a view to select the items when pressed, so we
structures, but if we want the user to select only one item, the property
add it to the navigation bar.
only needs to store one optional PhotosPickerItem structure, as shown next.
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
Listing 18-1: Creating a Photo picker
use the onChange() modifier. If a new image is selected, we start an

asynchronous task to call the loadTransferable() method on the selected item.
import SwiftUI
import PhotosUI 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
struct ContentView: View { assign it to a picture property to show it on the screen.
@State private var selected: PhotosPickerItem?
@State private var picture: UIImage?
Figure 18-1: Photo Library’s interface (center)
var body: some View {
NavigationStack {
VStack {
Image(uiImage: picture ?? UIImage(named: "nopicture")!)
.resizable()
.scaledToFit()
Spacer()
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
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) Do It Yourself: Create a Swift file called ApplicationData.swift for the
.resizable()
.scaledToFit() model in Listing 18-2. Update the ContentView view with the code in
} Listing 18-3. Remember to inject the ApplicationData object into the
}
}.padding() environment for the app and the previews (Chapter 7, Listing 7-4).
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Run the application on a device. Press the Select Photo button and
Button("Deselect") { select multiple pictures. You should see the selected pictures on the
list, as shown in Figure 18-2. At the moment of writing, the state Camera
property is not modified when all the items are deselected. If that’s 
still the case, you can add a button to remove all the items and clean
the selection, as we did in this example. 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.
isSourceTypeAvailable(SourceType)—This type method returns a editedImage, imageURL, livePhoto, mediaMetadata, mediaType, mediaURL, and
Boolean value that indicates if the source specified by the argument is originalImage.
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
struct ImagePicker: UIViewControllerRepresentable { This representable view controller creates an instance of the
@Binding var path: NavigationPath UIImagePickerController class and assigns the ImagePickerCoordinator object as its
@Binding var picture: UIImage?
delegate. Next, it checks if the camera is available and configures the
func makeUIViewController(context: Context) -> UIImagePickerController { controller in case of success or shows a message on the console otherwise.
The value camera is assigned to the sourceType property to tell the controller }
var body: some View {
that we are going to get the picture from the camera, an array with the NavigationStack(path: $contentData.path) {
value public.image is assigned to the mediaTypes property to set images as VStack {
HStack {
the media we want to retrieve, the allowEditing property is set as false to not Spacer()
let the user edit the image, and the value photo is assigned to the NavigationLink("Get Picture", value: "Open Picker")
}.navigationDestination(for: String.self, destination: { _ in
cameraCaptureMode property to allow the user only to take pictures. ImagePickerView
The camera’s interface includes buttons to control the camera and take the })
picture. After the user takes the picture, a new set of buttons appear to Image(uiImage: contentData.picture ?? UIImage(named: "nopicture")!)
.resizable()
allow the user select the picture or take another one. If the user decides to .scaledToFill()
use the current picture, the controller calls the .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.clipped()
imagePickerController(didFinishPickingMediaWithInfo:) method on its delegate to
Spacer()
report the action. This method receives a parameter called info that we can }.padding()
read to get the media returned by the controller and process it (store it in a }.statusBarHidden()
}
file, Core Data, or show it on the screen). In our example, we read the }
value of the originalImage key to get a UIImage object that represents the 
image taken by the user and assign this object to a state property to make
it available for the view. Notice that we have also implemented the This view creates an instance of the ImagePicker structure and declares it as
imagePickerControllerDidCancel() method in the coordinator to dismiss the
the destination of a NavigationLink button. When the button is pressed, the
controller when the user presses the Cancel button. view opens. If the user takes a picture and decides to use it, the picture is
The view must include a button to open the Image Picker Controller and an assigned to the picture property by the delegate method and the Image view
Image view to show the picture taken by the user.
is updated to display it on the screen.
Listing 18-5: Defining the interface to take pictures Do It Yourself: Create a Multiplatform project. Create a Swift file
 called ImagePicker.swift for the code in Listing 18-4. Update the
import SwiftUI ContentView.swift file with the code in Listing 18-5. Download the
image nopicture from our website and add it to the Asset Catalog.
class ContentData: ObservableObject {
@Published var path = NavigationPath()
Add the "Privacy - Camera Usage Description" option to the Info
@Published var picture: UIImage? panel in the app's settings with the text you want to show to the
}
struct ContentView: View { user. Run the application on a device and press the button. Take a
@ObservedObject var contentData = ContentData() picture and press the button to use it. You should see the photo on
var ImagePickerView: ImagePicker!
the screen.
init() {
ImagePickerView = ImagePicker(path: $contentData.path, picture: $contentData.picture)
Storing Pictures These are old functions defined in Objective-C and therefore require some
parameters that are not common to SwiftUI applications. But if all we want

is to store the image, we can declare the first argument and define the rest
as nil. For instance, we can add a button at the top of the screen to the
In the previous example, we show the picture on the screen, but we can
previous application that opens an Alert view with two buttons, one to
store it in a file or in the Core Data's Persistent Store. An alternative,
cancel the operation and another to save the current picture to the Photo
sometimes useful when working with the camera, is to store the picture in
Library. When the button to save the picture is pressed, we can call the
the device’s Photo Library so that it is accessible to other applications. The
UIImageWriteToSavedPhotosAlbum() function with a reference to the picture
UIKit framework offers two functions to store images and videos.
property, and the picture will be saved.
UIImageWriteToSavedPhotosAlbum(UIImage, Any?, Listing 18-6: Showing an Alert view when a picture is saved
Selector?, UnsafeMutableRawPointer?)—This function adds the 
image specified by the first argument to the camera roll. The second import SwiftUI
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 class ContentData: NSObject, ObservableObject {
@Published var path = NavigationPath()
selector that represents that method, and the last argument is an @Published var picture: UIImage?
}
object with data to pass to the method. struct ContentView: View {
UISaveVideoAtPathToSavedPhotosAlbum(String, Any?, @ObservedObject var contentData = ContentData()
@State private var showAlert: Bool = false
Selector?, UnsafeMutableRawPointer?)—This function adds the var ImagePickerView: ImagePicker!
video to the camera roll at the path indicated by the first argument.
init() {
The second argument is a reference to the object that contains the ImagePickerView = ImagePicker(path: $contentData.path, picture: $contentData.picture)
method we want to execute when the process is over, the third }
var body: some View {
argument is a selector that represents that method, and the last NavigationStack(path: $contentData.path) {
VStack {
argument is an object with additional data for the method. HStack {
Button("Share Picture") {
showAlert = true
IMPORTANT: To store pictures or videos in the device, we must ask }.disabled(contentData.picture == nil ? true : false)
the user’s authorization. As always, this is done from the Info panel Spacer()
NavigationLink("Get Picture", value: "Open Picker")
in the app's settings. In this case, we must add the "Privacy - Photo }.navigationDestination(for: String.self, destination: { _ in
Library Additions Usage Description" option with the message we ImagePickerView
})
want to show to the user when authorization is requested. .alert("Save Picture", isPresented: $showAlert, actions: {
Button("Cancel", role: .cancel, action: {
showAlert = false
})
Button("YES", role: .none, action: { Share Link
if let picture = contentData.picture {
UIImageWriteToSavedPhotosAlbum(picture, nil, nil, nil)
}

})
}, message: { Text("Do you want to store the picture in the Photo Library?") })
Another way to share information with other applications is a share sheet.
Image(uiImage: contentData.picture ?? UIImage(named: "nopicture")!)
This is a sheet provided by the system that includes icons to open the
.resizable() applications we can share content with, including options to copy the
.scaledToFill()
information and print it. SwiftUI includes the following view to open the
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.clipped() sheet.
Spacer()
}.padding()
}.statusBarHidden() ShareLink(String, item: Item, subject: Text?, message: Text?,
}
}
preview: SharePreview)—This initializer creates a button that
 presents a sheet to select the application with which we want to
share the data. The first argument is the button's title. The item
The process is the same as before. The image picker controller allows the argument is the value we want to share. (The value must conform to
user to take a picture and then calls the delegate method to process it. The
the Transferable protocol.) The subject argument is the item's title. The
picture is assigned to the picture property to display it on the screen, but
message argument is the item's description. And the preview
now we have an additional button to save the picture to the Photo Library.
argument is a structure that provides a representation of the item.
Do It Yourself: Update the ContentView.swift file with the code in
If we want to share an image, we must provide a preview. SwiftUI includes
Listing 18-6. Add the "Privacy - Photo Library Additions Usage
the SharePreview structure for this purpose.
Description" option to the Info panel in the app's settings to get
access to the Photo Library. (Remember that you also need the
SharePreview(String, image: Image)—This initializer creates a
"Privacy - Camera Usage Description" option to access the camera,
representation of the item to share. The first argument is the
as before.) Run the application on a device and take a picture. You
description of the item, and the image argument is an Image view that
should see the picture on the screen. Press the Share Picture button.
visually represents the item.
You should see an Alert View with the message "Picture Saved" and
the picture should be available in your 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.

struct ContentView: View { Figure 18-4: Share sheet
@ObservedObject var contentData = ContentData()
var ImagePickerView: ImagePicker!
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 Do It Yourself: Update the ContentView view from the previous
ImagePickerView
}) example with the code in Listing 18-7. Run the application on a
Image(uiImage: contentData.picture ?? UIImage(named: "nopicture")!) device. Press the Get Picture button and take a picture. Press the
.resizable()
.scaledToFill() Share Picture button. You should see the share sheet at the bottom
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.clipped()
of the screen. Select an app to share the image.
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.
Custom Camera of type AVMediaType with properties to define the type of media. The
 properties available to work with the cameras and microphones are
video and audio.
The UIImagePickerController controller is built from classes defined in the AV requestAccess(for: AVMediaType)—This asynchronous type
Foundation framework. This framework provides the code necessary to method asks the user for permission to access the device. The for
process media and control input devices, like the camera and the argument is a structure of type AVMediaType with properties to define
microphone. So we can use the classes in this framework directly to build the type of media. The properties available to work with the cameras
our own controller and customize the process and the interface. and microphones are video and audio.
Creating our own controller to access the camera and retrieve information
demands the coordination of several systems. We need to configure the
authorizationStatus(for: AVMediaType)—This type method
input from the camera and the microphone, process the data received returns a value that determines the status of the authorization to use
from these inputs, show a preview to the user, and generate the output in the device. The for argument is a structure of type AVMediaType with
the form of an image, live photo, video, or audio. Figure 18-5 illustrates all properties to define the type of media. The properties available to
the elements involved. work with the cameras and microphones are video and audio. The
method returns an enumeration of type AVAuthorizationStatus with the
Figure 18-5: System to capture media values notDetermined, restricted, denied, and authorized.
The first thing we need to do to build this structure is to determine the AVCaptureDeviceInput(device: AVCaptureDevice)—This
input devices. The AV Foundation framework defines the AVCaptureDevice initializer creates an input for the device specified by the device
class for this purpose. An instance of this class can represent any type of argument.
input device, including cameras and microphones. The following are some
of the methods included in the class to access and manage a device. In addition to inputs, we also need outputs to process the data captured by
the device. The framework defines subclasses of a base class called
default(for: AVMediaType)—This type method returns an AVCaptureOutput to describe the outputs. There are several subclasses
object that represents the default capture device for
AVCaptureDevice available, such as AVCaptureVideoDataOutput to process the frames of a video,
the media specified by the argument. The for argument is a structure and AVCaptureAudioDataOutput to get the audio, but the most frequently used
is the AVCapturePhotoOutput class used to capture a single video frame (take a AVCaptureVideoPreviewLayer(session: AVCaptureSession)—
picture). This class works with a delegate that conforms to the This initializer creates an AVCaptureVideoPreviewLayer object with a
AVCapturePhotoCaptureDelegate protocol, which among other methods defines preview layer connected to the capture session defined by the session
the following to return a still image. argument.
videoGravity—This property defines how the video adjust its size to
photoOutput(AVCapturePhotoOutput,
the size of the preview layer. It is an enumeration of type
didFinishProcessingPhoto: AVCapturePhoto, error: Error?)—
AVLayerVideoGravity with the values resizeAspect, resizeAspectFill, and resize.
This method is called on the delegate after the image is captured. The
didFinishProcessingPhoto argument is a container with information connection—This property returns an object of type
about the image, and the error argument is used to report errors. that defines the connection between the capture
AVCaptureConnection
session and the preview layer.
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 The input, output, and preview layers are connected to the capture session
inputs and outputs and determine when the process begins and ends by by objects of the AVCaptureConnection class. The class manages the
calling the following methods. information for the connection, including ports and data. The following are
some of the properties provided by this class.
addInput(AVCaptureInput)—This method adds an input to the
capture session. The argument represents the input device we want videoOrientation—This property sets or returns the orientation of
to add. the video. It is an enumeration of type AVCaptureVideoOrientation with
the values portrait, portraitUpsideDown, landscapeRight, and landscapeLeft.
addOutput(AVCaptureOutput)—This method adds an output to
the capture session. The argument represents the output we want to isVideoOrientationSupported—This property returns a Boolean
generate from the capture session. value that determines whether it is possible to set the video
orientation or not.
startRunning()—This method starts the capture session.
stopRunning()—This method stops the capture session. 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
The framework also defines the AVCaptureVideoPreviewLayer class to show a take a picture with the camera, and an Image view to show it on the screen.
preview to the user. This class creates a sublayer to display the video
captured by the input device. The class includes the following initializer Figure 18-6: Interface for a custom camera
and properties to create and manage the preview layer.

import SwiftUI
import AVFoundation
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?

var cameraView: CustomPreviewLayer!
The process to activate the camera and get the picture taken by the user is var viewData: ViewData
independent of the interface, but if we want to let the user see the image
override init() {
coming from the camera, we must create a preview layer and add it to a cameraView = CustomPreviewLayer()
UIKit view. As we have seen before, UIKit views are created from the UIView viewData = ViewData()
super.init()
class, so we need to define a representable view (see Chapter 16).
Task(priority: .background) {
Listing 18-8: Defining a UIView to show the camera's preview video await receiveNotification()
}

}
import SwiftUI func receiveNotification() async {
let center = NotificationCenter.default
struct CustomPreviewLayer: UIViewRepresentable { let name = await UIDevice.orientationDidChangeNotification
let view = UIView() for await _ in center.notifications(named: name, object: nil) {
if viewData.captureSession != nil {
await MainActor.run {
func makeUIView(context: Context) -> UIView { viewData.previewLayer.frame = cameraView.view.bounds
return view let videoOrientation = getCurrentOrientation()
} let connection = viewData.previewLayer.connection
func updateUIView(_ uiView: UIView, context: Context) { } connection?.videoOrientation = videoOrientation
} }
 }
}
}
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.
This code is only the first part of our model, we still need to add a few
Listing 18-9: Defining the properties we need to manage the camera 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 Listing 18-11: Initializing the camera
instantiate the UIView view we need to show the preview layer. Because 
these properties are required by multiple methods, we declare them in a func prepareCamera() {
viewData.captureSession = AVCaptureSession()
separate class called ViewData. When the model is initialized, we create an if let device = AVCaptureDevice.default(for: AVMediaType.video) {
instance of this class and the representable view (CustomPreviewLayer), and if let input = try? AVCaptureDeviceInput(device: device) {
then run an asynchronous method to listen to the viewData.captureSession.addInput(input)
orientationDidChangeNotification notification to be able to adapt the size and viewData.stillImage = AVCapturePhotoOutput()
orientation of the preview when the device's orientation changes (see viewData.captureSession.addOutput(viewData.stillImage)
showCamera()
Chapter 14, Listing 14-11). } else {
The next step is to define a method to ask for the user's permission to print("Not Authorized")
}
access the camera. This is done automatically when we use a } else {
UIImagePickerController controller, but we have to do it ourselves in a custom print("Not Authorized")
}
controller using the type methods provided by the AVCaptureDevice class. The }
following is the method we must add to our model for this purpose. 
Listing 18-10: Asking for permission to use the camera 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
func getAuthorization() async { error, we use it first. This initializer creates an object that manages the
let granted = await AVCaptureDevice.requestAccess(for: .video) input for the capture device. If the initializer is successful, we add it to the
await MainActor.run {
if granted { capture session with the addInput() method and then create the output. For
self.prepareCamera() this example we have decided to use the session to capture a still image, so
} else {
print("Not Authorized") 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 requestAccess() method is asynchronous; it waits for the user to respond the screen. In this method, we must create the layer and set its size and
and returns a value of type Bool to report the result. If the user grants orientation.
access, we execute a method called prepareCamera(). This is where we begin
to build the network of objects introduced in Figure 18-5. The method Listing 18-12: Showing the video from the camera on the screen
must get a reference to the current capture device for video and create the 
inputs and outputs we need to capture a still image (to take a picture). func showCamera() {
let width = cameraView.view.bounds.size.width
let height = cameraView.view.bounds.size.height
preview layer and stores a UIImage.Orientation value in the imageOrientation
viewData.previewLayer = AVCaptureVideoPreviewLayer(session: viewData.captureSession) property to set the image orientation later.
viewData.previewLayer.videoGravity = .resizeAspectFill
viewData.previewLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
Listing 18-13: Detecting the device's orientation
let videoOrientation = getCurrentOrientation() 
let connection = viewData.previewLayer.connection
connection?.videoOrientation = videoOrientation func getCurrentOrientation() -> AVCaptureVideoOrientation {
var currentOrientation: AVCaptureVideoOrientation!
let deviceOrientation = UIDevice.current.orientation
let layer = cameraView.view.layer
layer.addSublayer(viewData.previewLayer)
Task(priority: .background) { switch deviceOrientation {
viewData.captureSession.startRunning() case .landscapeLeft:
} currentOrientation = AVCaptureVideoOrientation.landscapeRight
} viewData.imageOrientation = .up
 case .landscapeRight:
currentOrientation = AVCaptureVideoOrientation.landscapeLeft
viewData.imageOrientation = .down
The AVCaptureVideoPreviewLayer() initializer creates a layer that we have to case .portrait:
adjust to the size of its view and add as a sublayer of the view’s layer. But currentOrientation = AVCaptureVideoOrientation.portrait
setting the position and size of the layer does not determine how its viewData.imageOrientation = .right
case .portraitUpsideDown:
content is going to be shown. The video coming from the camera could currentOrientation = AVCaptureVideoOrientation.portraitUpsideDown
have a different size and orientation. How the video is going to adjust to viewData.imageOrientation = .left
default:
the size of the layer is determined by the value of the layer’s videoGravity if UIDevice.current.orientation.isLandscape {
property, and the orientation is set in the connection established between currentOrientation = AVCaptureVideoOrientation.landscapeRight
viewData.imageOrientation = .up
the capture session and the preview layer. That is why, after setting the } else {
value of the videoGravity property and the layer’s frame, we get a reference currentOrientation = AVCaptureVideoOrientation.portrait
viewData.imageOrientation = .right
to the connection from the layer’s connection property. By modifying the }
videoOrientation property of the connection, we can adjust the orientation break
}
according to the device’s orientation and finally add the sublayer to the return currentOrientation
view’s layer with the addSublayer() method. Once the sublayer is ready, the }

capture session is initiated with the startRunning() method. (The system
requires this method to be executed in a background thread.)
IMPORTANT: The camera always encodes the image in its native
The device's orientation is not only required to define the orientation of
orientation, which is landscape-right. In consequence, when the
the preview layer, but also the orientation of the image taken by the
device is in portrait mode, we have to set the orientation of the
camera. In our example, the value of the videoOrientation property is
determined by a method called getCurrentOrientation(). This method returns image to right, when it is in landscape-left mode, we have to set the
the AVCaptureVideoOrientation value we need to set the orientation of the image's orientation to down, and when it is in landscape-right mode,
we have to set it to up. Also, the landscape orientation of the video The following are the properties available in this class to configure the
is the opposite of the device, therefore when the device is in the image and the preview.
landscape-right orientation, the video orientation is landscape-left,
and vice versa. previewPhotoFormat—This property sets or returns a dictionary
with keys and values that determine the characteristics of the preview
At this point, the video is playing on the screen and the system is ready to image. The keys available are kCVPixelBufferPixelFormatTypeKey
perform a capture. The process to capture an image is initiated by the (uncompressed format), kCVPixelBufferWidthKey (width) and
output object. The AVCapturePhotoOutput class we use to capture a still image kCVPixelBufferHeightKey (height).
offers the following method for this purpose. flashMode—This property sets or returns the flash mode used when
the image is captured. It is an enumeration of type FlashMode with the
capturePhoto(with: AVCapturePhotoSettings, delegate:
values on, off, and auto.
AVCapturePhotoCaptureDelegate)—This method initiates a photo
capture with the settings specified by the with argument. The
isHighResolutionPhotoEnabled—This property is a Boolean value
that determines if the image is going to be taken in high resolution.
delegate argument is a reference to the object that implements the
methods of the AVCapturePhotoCaptureDelegate protocol to receive the
To capture an image, we have to define the settings with an
data generated by the output.
AVCapturePhotoSettings object, call the capturePhoto() method of the
AVCapturePhotoOutput object, and define the delegate method that is going to
The type of photo captured by the output is determined by an
receive the image. The following is the method we need to add to the
AVCapturePhotoSettings object. The class includes multiple initializers. The
model to take the picture.
following are the most frequently used.
Listing 18-14: Taking a picture
AVCapturePhotoSettings()—This initializer creates an 
AVCapturePhotoSettings object with the format by default. func takePicture() {
let settings = AVCapturePhotoSettings()
AVCapturePhotoSettings(format: Dictionary)—This initializer
viewData.stillImage.capturePhoto(with: settings, delegate: self)
creates a AVCapturePhotoSettings object with the format specified by the }
format argument. The argument is a dictionary with keys and values 
to set the characteristics of the image. Some of the keys available are
kCVPixelBufferPixelFormatTypeKey (uncompressed format), AVVideoCodecKey
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
(compressed format), AVVideoQualityKey (quality).
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 access the screen to get the current scale. The screen is managed by an
delegate method in the model. The following is our implementation of this object of the UIScreen class that is automatically created for the device and
method. assigned to a property of the Scene. Therefore, to access the screen and
get the scale, we must read the UIWindowScene object that controls the
Listing 18-15: Processing the image current Scene from the connectedScenes property of the UIApplication object.
 We have introduced this object in Chapter 14. It is created by the system to
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: control the application. The object is accessible from a type property
AVCapturePhoto, error: Error?) {
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene
provided by the class called shared and it includes the connectedScenes
let scale = scene?.screen.scale ?? 1 property to return references to all the Scenes opened for the app. In this
example, we are developing an application for mobile devices, so all we
if let imageData = photo.cgImageRepresentation() { need is to access the first Scene available. The UIWindowScene object includes
picture = UIImage(cgImage: imageData, scale: scale, orientation: viewData.imageOrientation) the screen property to return a reference to the UIScreen object that
path = NavigationPath()
} represents the screen, and the UIScreen object includes the scale property to
} return the current scale and the bounds property to return the screen's size,

among others. With these values, we create the UIImage object and assign it
to the picture property to update the view, which receives this object and
The photoOutput(AVCapturePhotoOutput, didFinishProcessingPhoto:) method receives
displays the image on the screen, as shown next.
the picture produced by the camera. The value received by this method is
an object of type AVCapturePhoto, which is a container with information
Listing 18-16: Showing the image
about the image. The class includes two convenient methods to get the

data representing the image.
struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
fileDataRepresentation()—This method returns a data
representation of the image that we can use to create a UIImage var body: some View {
NavigationStack(path: $appData.path) {
object. VStack {
HStack {
cgImageRepresentation()—This method returns the image as a Spacer()
CGImage object (Core Graphics). NavigationLink("Take Picture", value: "Open Camera")
}
.navigationDestination(for: String.self, destination: { _ in
In our example, we have implemented the cgImageRepresentation() method CustomCameraView()
})
because the UIImage class defines a convenient initializer to create an image
Image(uiImage: appData.picture ?? UIImage(named: "nopicture")!)
from a CGImage object that includes the scale and orientation. We get the .resizable()
orientation from the imageOrientation property set by the getCurrentOrientation() .scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
method, but the image is created to the scale of the screen, so we need to .clipped()
Spacer()
}.padding()
Task(priority: .high) {
.navigationBarHidden(true)
await appData.getAuthorization()
}.statusBar(hidden: true)
}
}
}
}
.onDisappear {

let device = UIDevice.current
device.endGeneratingDeviceOrientationNotifications()
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
Listing 18-17: Taking a picture 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
import SwiftUI 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
struct CustomCameraView: View { call the takePicture() method to capture the image. Once the image is
@EnvironmentObject var appData: ApplicationData
processed, the view is dismissed by the delegate method, and the picture
var body: some View {
is shown on the screen.
ZStack {
appData.cameraView
VStack {
Do It Yourself: Create a Multiplatform project. Download the
Spacer() nopicture image from our website and add it to the Asset Catalog.
HStack {
Button("Cancel") { Create a Swift file called CustomPreviewLayer.swift for the code in
appData.path = NavigationPath() Listing 18-8, and another called ApplicationData.swift for the model
}
Spacer() in Listing 18-9. Add to the model the methods in Listings 18-10, 18-
Button("Take Picture") {
appData.takePicture()
11, 18-12, 18-13, 18-14 and 18-15. Update the ContentView view with
} the code in Listing 18-16. Create a SwiftUI View file called
}.padding()
.frame(height: 80) CustomCameraView.swift for the view in Listing 18-17. Remember to
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 0.8)) add the option "Privacy - Camera Usage Description" to the Info
}
} panel in the app's settings and to inject the ApplicationData object into
.edgesIgnoringSafeArea(.all)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
the environment for the app and the previews (Chapter 7, Listing 7-
.navigationBarHidden(true) 4). Run the application on a device and take a picture.
.onAppear {
let device = UIDevice.current
device.beginGeneratingDeviceOrientationNotifications()
18.2 Video Video Player
 
Recording and playing videos is probably as important to users as taking SwiftUI defines the VideoPlayer view to play videos. This view provides all the
and displaying pictures. As with images, Apple frameworks include pre- controls required to play, stop, and move the video back and forth. The
built tools for playing videos and creating a custom video player. 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.
play()—This method begins playback. The VideoPlayer view and the AVPlayer class are defined in the AVKit
pause()—This method pauses playback. 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
addPeriodicTimeObserver(forInterval: CMTime, queue: the view, we need to check this property and show the VideoPlayer view if
DispatchQueue?, using: Closure)—This method adds an observer there is a video to play.
that executes a closure every certain period of time. The forInterval
argument determines the time between executions, the queue Listing 18-19: Playing a video
argument is the queue in which the closure should be executed (the 
main thread is recommended), and the using argument is the closure import SwiftUI
import AVKit
we want to execute. The closure receives a value of type CMTime with
the time at which the closure was called. struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
The VideoPlayer view requires an AVPlayer object to play the video, and this
var body: some View {
object loads the video from a URL. The best way to prepare this if appData.player != nil {
information is with a model. In the following model, we get the URL of a VideoPlayer(player: appData.player)
.ignoresSafeArea()
video in the bundle called videotrees.mp4, and create an AVPlayer object } else {
with this value. Text("Video not available")
}
}
Listing 18-18: Preparing the video to be played }


import SwiftUI
import AVKit Figure 18-7: Standard video player
init() {
let bundle = Bundle.main
if let videoURL = bundle.url(forResource: "videotrees", withExtension: "mp4") {
player = AVPlayer(url: videoURL)
}
} 
}

Do It Yourself: Create a Multiplatform project. Download the
videotrees.mp4 file from our website and add it to your project
(make sure that the target is selected). Create a Swift file called
ApplicationData.swift for the model in Listing 18-18. Update the struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
ContentView view with the code in Listing 18-19. Remember to inject
the ApplicationData object into the environment for the app and the var body: some View {
if appData.player != nil {
previews (Chapter 7, Listing 7-4). Run the application in the iPhone VideoPlayer(player: appData.player, videoOverlay: {
simulator. Press play to play the video. VStack {
Text("Title: Trees at the park")
.font(.title)
In the previous example, the video doesn't play until the user presses the .padding([.top, .bottom], 8)
.padding([.leading, .trailing], 16)
play button. But we can implement the AVPlayer properties and methods to .foregroundColor(.black)
control the video programatically. For instance, the following example .background(.ultraThinMaterial)
.cornerRadius(10)
starts the video as soon as the view is loaded. .padding(.top, 8)
Spacer()
}
Listing 18-20: Automatically playing a video })
 .ignoresSafeArea()
} else {
struct ContentView: View {
Text("Video not available")
@EnvironmentObject var appData: ApplicationData }
}
var body: some View { }
if appData.player != nil { 
VideoPlayer(player: appData.player)
.onAppear {
appData.player.play() 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
.ignoresSafeArea()
} else { provide additional information, as in this case. The result is shown below.
Text("Video not available")
}
} Figure 18-8: Overlay views
}

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.

Listing 18-21: Presenting views over the video

An asset contains static information and cannot manage its state when it is AVPlayer(playerItem: AVPlayerItem?)—This initializer creates an
being played. To control the asset, the framework defines the AVPlayerItem object to play the media represented by the playerItem
AVPlayer
class. With this class, we can reference an asset and manage the timeline. argument.
The class includes multiple initializers. The following is the most frequently
used.
The last object required by the structure is the one in charge of displaying that represents the time in seconds. The seconds argument
the media. This is a subclass of the CALayer class called AVPlayerLayer that determines the seconds we want to assign to the structure, and the
provides the code necessary to draw the frames on the screen. The class preferredTimescale argument determines the scale we want to use. A
includes the following initializer and property to create and configure the value of 1 preserves the value in seconds assigned to the first
layer.
argument.
AVPlayerLayer(player: AVPlayer)—This initializer creates an zero—This type property returns a CMTime structure with the value 0.
AVPlayerLayer object associated with the player specified by the player
The CMTime structure also includes multiple properties to set and
argument.
retrieve the values. The following are the most frequently used.
videoGravity—This property defines how the video adjusts its size
to the preview layer’s size. It is a AVLayerVideoGravity structure with the seconds—This property returns the time of a CMTime structure in
type properties resize, resizeAspect, and resizeAspectFill. seconds. It is of type Double.
value—This property returns the value of a CMTime structure.
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 timescale—This property returns the time scale of a CMTime
is not suitable for media playback, the framework implements, among structure.
other things, the CMTime structure from an old framework called Core
Media. The structure contains multiple values to represent time as a To create a custom video player, we must load the asset (AVURLAsset),
fraction. The most important are value and timescale, which represent the create the item to manage the asset (AVPlayerItem), add the item to the
numerator and denominator, respectively. For example, if we want to player (AVPlayer), and associate the player to a layer to display the media on
create a CMTime structure to represent 0.5 seconds, we may declare 1 as the screen (AVPlayerLayer). But playing the media requires an additional
the numerator and 2 as the denominator (1 divided by 2 is equal to 0.5). step. The media does not become immediately available, it has to be
The class includes initializers and type properties to create these values. loaded and prepared for playback, so we cannot play it right away, we must
The following are the most frequently used. wait until it is ready. The media status is reported by the status property of
the AVPlayerItem object, so we must observe the value of this property to
CMTime(value: CMTimeValue, timescale: CMTimeScale)—This start playing the media only after it is equal to readyToPlay. This requires the
initializer creates a CMTime structure with the values specified by the use of a technique called KVO (Key-Value Observing). KVO was developed
value and timescale arguments. The arguments are integers of type in Objective-C and it is used to turn an object into an observer of a
Int64 and Int32, respectively.
property. When the value of that property changes, the object executes a
method to report the change. The methods to add, remove, and respond
CMTime(seconds: Double, preferredTimescale: CMTimeScale) to an observer are defined in the NSObject class. The following are the three
—This initializer creates a CMTime structure from a floating-point value methods involved in the process.
return view
}
addObserver(NSObject, forKeyPath: String, options: func updateUIView(_ uiView: UIView, context: Context) { }
NSKeyValueObservingOptions, context: }

UnsafeMutableRawPointer?)—This method adds an observer to
the object. The first argument is the object that responds to the Now that we have the representable view, the next step is to build the
notification, the forKeyPath argument is a string with the name or the video player and then call the play() method from the observeValue() method
path to the property we want to observe, the options argument is an to start playing the video as soon as it is ready.
enumeration that determines the values that are going to be sent to
the method that responds to the notification (possible values are new, Listing 18-23: Building a custom video player
old, initial, and prior), and the context argument is a generic value that 
import SwiftUI
identifies the observer (used when a class and its subclasses observe import AVFoundation
the same property).
class ViewData: NSObject {
removeObserver(NSObject, forKeyPath: String)—This method var playerItem: AVPlayerItem!
removes an observer. The arguments are the same values specified in var player: AVPlayer!
var playerLayer: AVPlayerLayer!
the addObserver() method when the observer was added.
func setObserver() {
observeValue(forKeyPath: String?, of: Any?, change: playerItem.addObserver(self, forKeyPath: "status", options: [], context: nil)
Dictionary?, context: UnsafeMutableRawPointer?)—This }
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change:
method is called by the observer to report a change in the value of [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if playerItem.status == .readyToPlay {
the observed property.
playerItem.removeObserver(self, forKeyPath: "status")
player.play()
}
The following example implements these methods to listen to the status }
property of the AVPlayerItem object. As before, we need a representable }
class ApplicationData: ObservableObject {
view with an empty UIView object to present the video on the screen.
var customVideoView: CustomPlayerView!
var viewData: ViewData
Listing 18-22: Building a custom video player
 init() {
customVideoView = CustomPlayerView()
import SwiftUI viewData = ViewData()
How we control the process and respond to the interface depends on the let interval = CMTime(value: 1, timescale: 2)
viewData.player.addPeriodicTimeObserver(forInterval: interval, queue:
requirements of our application. For this example, we have decided to DispatchQueue.main, using: { time in
define two states, one to indicate if the video is playing or not, and another let duration = self.viewData.playerItem.duration
let position = time.seconds / duration.seconds
to determine the size of the progress bar. The following are the changes we self.progress = CGFloat(position)
must introduce to our model to allow the user to play and pause the video })
Task(priority: .background) {
and to update the progress bar. await receiveNotification()
}
}
Listing 18-25: Preparing the video player func receiveNotification() async {
 let center = NotificationCenter.default
let name = await UIDevice.orientationDidChangeNotification
import SwiftUI for await _ in center.notifications(named: name, object: nil) {
import AVFoundation
if viewData.playerItem != nil {
await MainActor.run {
class ViewData: NSObject { viewData.playerLayer.frame = customVideoView.view.bounds
var playerItem: AVPlayerItem! }
var player: AVPlayer! }
var playerLayer: AVPlayerLayer! }
} }
class ApplicationData: ObservableObject { func playVideo() {
@Published var playing: Bool = false if viewData.playerItem.status == .readyToPlay {
@Published var progress: CGFloat = 0 if playing {
var customVideoView: CustomPlayerView! viewData.player.pause()
var viewData: ViewData playing = false
} else {
viewData.player.play()
init() {
playing = true
customVideoView = CustomPlayerView() }
viewData = ViewData()
}
}
let bundle = Bundle.main }
let videoURL = bundle.url(forResource: "videotrees", withExtension: "mp4") 
The player is ready. It is time to define the interface. In this occasion, we
In this example, we include a method called playVideo() to execute when the need to present the representable view inside a ZStack so we can display a
user presses the Play button. The method checks whether the media can toolbar on top (see Figure 18-10).
be played or not, and then performs an action according to the value of the
playing property. If the video is playing, we pause it, and if it is paused, we Listing 18-26: Playing and pausing the video
play it. In either case, we update the value of the playing property to reflect 
the new state. struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
To calculate the size of the progress bar, we must implement an observer.
But this is not a KVO observer like the one implemented before. Normal var body: some View {
observers are not fast enough, so the AVFoundation framework offers the ZStack {
appData.customVideoView
addPeriodicTimeObserver() method to create an observer that provides a more .ignoresSafeArea()
accurate response. The method requires a CMTime value to determine the VStack {
Spacer()
frequency at which the code will be executed, a reference to the main HStack {
queue, and a closure with the code we want to execute every time the Button(appData.playing ? "Pause" : "Play") {
appData.playVideo()
observer is triggered. In this example, we create a CMTime value to }.frame(width: 70)
represent a time of 0.5 seconds, and then use it in the call of the .foregroundColor(.black)
GeometryReader { geometry in
addPeriodicTimeObserver() method to register the observer. After this, the HStack {
closure provided to the observer will be executed every 0.5 seconds during Rectangle()
.fill(Color(red: 0, green: 0.4, blue: 0.8, opacity: 0.8))
playback. In this closure, we get the current time and the duration of the .frame(width: geometry.size.width * appData.progress, height: 20)
video in seconds and calculate the progression by turning seconds into a Spacer()
}
value between 0.0 and 1.0 that we can later convert into points to display }.padding(.top, 15)
the progress bar on the screen. }
.padding([.leading, .trailing])
.frame(height: 50)
IMPORTANT: The addPeriodicTimeObserver() method doesn't work with .background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 0.8))
}
Swift concurrency. Instead, it requires the thread to be defined by a }
DispatchQueue object. This is an old class defined by the Dispatch }
}
framework to create asynchronous tasks. The class includes a type 
property called main to define a task for the main queue (the Main
Actor), and this is how we make sure that the closure assigned to The toolbar includes a button and a Rectangle view that represents the
this method runs in the main thread. 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 use a CMTime value of 0 to move the playback to the beginning of the video
this property contains a value between 0.0 and 1.0, the operation returns and then reset the playing and progress properties to allow the user to play
the value we need to set the width of the bar and show the progression on the video again.
the screen.
Listing 18-28: Rewinding the video
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 func rewindVideo() async {
let center = NotificationCenter.default
application. You should see the video player illustrated in Figure 18- let name = NSNotification.Name.AVPlayerItemDidPlayToEndTime
for await _ in center.notifications(named: name, object: nil) {
10. let finished = await viewData.playerItem.seek(to: CMTime.zero)
if finished {
await MainActor.run {
The observer added by the addPeriodicTimeObserver() method is not the only playing = false
way to get information from the player over time. The AVPlayerItem class progress = 0
}
also defines several notifications to report events that happen during }
media playback. For example, we can listen to the }
}
AVPlayerItemDidPlayToEndTime notification to know when the video finishes

playing. For this purpose, we need to define a method in the model to
listen and respond to the notification, and a task to call this method as Do It Yourself: Add the task in Listing 18-27 at the end of the
soon as the representable view is created. The following is the task we ApplicationData's initializer. Add the method in Listing 18-28 to the end
need to add in the ApplicationData's initializer.
of the ApplicationData class. Run the application on the iPhone
simulator. Press play and wait until the video is over. The player
Listing 18-27: Executing an asynchronous method to detect the end of the
should reset itself and you should be able to play the video again.
video

Task(priority: .background) {
If we want to play multiple videos in sequence, we could use the
await rewindVideo() AVPlayerItemDidPlayToEndTime notification to assign a new asset to the AVPlayer
}

object, but the framework offers a subclass of the AVPlayer class called
AVQueuePlayer designed specifically to manage a list of videos. The class
In the rewindVideo() method, we must listen to the creates a playlist from an array of AVPlayerItem objects. The following are the
AVPlayerItemDidPlayToEndTime notification and prepare the video to be played initializer and some of its methods.
again. For this purpose, the AVPlayerItem class offers the seek() method. This
method moves the playback to the time specified by the argument and AVQueuePlayer(items: [AVPlayerItem])—This initializer creates a
executes a closure after the process is over. In this case, we are going to play list with the items specified by the items argument.
advanceToNextItem()—This method advances the playback to the
init() {
next item on the list. customVideoView = CustomPlayerView()
viewData = ViewData()
insert(AVPlayerItem, after: AVPlayerItem?)—This method inserts
let bundle = Bundle.main
a new item on the list. let videoURL1 = bundle.url(forResource: "videotrees", withExtension: "mp4")
remove(AVPlayerItem)—This method removes an item from the let videoURL2 = bundle.url(forResource: "videobeaches", withExtension: "mp4")
AVPlayerItemobjects to represent them. The AVQueuePlayer object is define 18.3 Color Picker
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. Along with all the tools provided by SwiftUI and UIKit to control the
camera, play videos, and manage pictures, SwiftUI also includes the
Do It Yourself: Update the ApplicationData.swift file with the code in ColorPicker view to allow the user to pick a color. The view creates a button
Listing 18-29. This example was designed to work with the ContentView that opens a predefined interface with tools to select and configure a color.
The following is the view's initializer.
view defined in Listing 18-24. Download the videobeaches.mp4 and
videotrees.mp4 videos from our website and add them to your
ColorPicker(String, selection: Binding, supportsOpacity: Bool)
project. (Remember to check the option Add to Target.) Run the
—This initializer creates a color picker. The first argument provides the
application. The videos should be played one after another.
label to show next to the button, the selection argument is a Binding
property that stores a Color view with the color selected by the user,
and the supportsOpacity argument determines whether the user will be
allowed to set the color's opacity. The value by default is true.
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.
Xcode also allows us to provide images for each platform. The options are
Figure 19-2: Mac destinations 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 Conditional Code
running on the Mac.

Figure 19-4: Mac images
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.
This is a very simple example but illustrates how to work with these types #if os(macOS)
.foregroundColor(.red)
of conditionals. If we run this project on a mobile device, we get the #else
message "Mobile Application", but on the Mac, we get a window of a size .foregroundColor(.green)
#endif
of 500 by 350 points with the message "Mac Application".
}.frame(width: 500, height: 350)
}
}
Figure 19-5: Mac application 
Do It Yourself: Create a Multiplatform project. Update the ContentView Figure 19-6: Platform selection
view with the code in Listing 19-1. From the Xcode's toolbar, click on
the My Mac option (Figure 19-3). Press the Play button to run the
application. You should see a window with a message at the center,
as illustrated in Figure 19-5.
Menu To modify the options of standard menus, we can use the CommandGroup
structure instead. The following are the structure's initializers.

CommandGroup(after: CommandGroupPlacement, addition:
Mac applications include a menu bar that is displayed at the top of the
screen for easy access to the application's key features.
Closure)—This structure defines a menu option. The after argument
is a structure that specifies the option after which the new option will
Figure 19-8: Standard menu be added. The option is defined by the closure assigned to the
addition argument.

CommandGroup(before: CommandGroupPlacement,
The options included with the menu are predefined by the system and
addition: Closure)—This structure defines a menu option. The
provide basic functionality to the app. To introduce changes and add before argument is a structure that specifies the option before which
custom functionality, SwiftUI includes the following modifier. the new option will be added. The option is defined by the closure
assigned to the addition argument.
commands(content: Closure)—This modifier modifies the Scene CommandGroup(replacing: CommandGroupPlacement,
to include the menus and options defined by the closure assigned to addition: Closure)—This structure defines a menu option. The
the content argument. replacing argument is a structure that specifies the option that will be
replaced by the new option. The option is defined by the closure
This modifier is applied to the Scene (the WindowGroup structure in the App assigned to the addition argument.
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 The CommandGroup structure determines the position of the new option
includes the CommandMenu and CommandGroup structures. The CommandMenu based on the position of a standard option. The standard options are
structure is used to create new menus. The following is the structure's represented by a CommandGroupPlacement structure. The structure includes
initializer. type properties to return instances that represent all the standard options
available. The properties are appInfo, appSettings, appTermination, appVisibility,
CommandMenu(String, content: Closure)—This structure systemServices, importExport, newItem, printItem, saveItem, pasteboard, textEditing,
creates a menu to add to the menu bar. The first argument specifies textFormatting, undoRedo, sidebar, toolbar, singleWindowList, windowArrangement,
the menu's title, and the closure assigned to the content argument windowList, windowSize, and help.
defines the menu's option. The new menu is inserted between the Because the commands() modifier applies to the Scene, we can only
View and Window menus. implement it in the App structure. For instance, in the following example
we use it to add a new menu to the menu bar.
Listing 19-4: Adding a menu to the menu bar options. When selected, these options print a message on the
 console, but you can use them for anything you want, such as
import SwiftUI modifying a state property to update the interface or executing a
method in the model, as we will see later.
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
Menu options may be connected to keys on the keyboard, so when the
ContentView() user presses the keys, the action associated with the option is executed.
}
#if os(macOS)
SwiftUI offers the following modifier to create these keyboard shortcuts.
.commands {
CommandMenu("Options") { keyboardShortcut(KeyEquivalent, modifiers: EventModifiers)
Button("Option 1") {
print("This is the option 1") —This modifier assigns a keyboard shortcut to a menu option. The
}
Button("Option 2") { first argument can be a string representation of the key (e.g., "A") or a
print("This is the option 2") function key represented by a KeyEquivalent structure. The modifiers
}
} argument is an array of key modifiers that must be pressed along with
} the main key to perform the action. The structure includes type
#endif
} properties to return instances for every modifier key available. The
}

properties are all, capsLock, command, control, numericPad, option, and shift.
The menu options are generated with Button views. In this example, we The KeyEquivalent structure can be represented by a string, and the string
include two: Option 1 and Option 2. The new menu is added between the can include a letter, a number, or a punctuation character. The structure
View and Window menus, as shown below. also includes type properties to return instances that represent function
keys. The properties available are upArrow, downArrow, leftArrow, rightArrow,
Figure 19-9: Custom menu clear, delete, deleteForward, end, escape, home, pageDown, pageUp, return, space, and
tab. The following example creates a shortcut for the second option with
the A and Shift keys.
In addition to our own menus, we can also add options to the standard Listing 19-7: Removing options
menus or replace the options provided by the system with the 
CommandGroup structure. This structure defines three initializers that we can .commands {
use to insert a new option before or after a system option, and also replace CommandGroup(replacing: .newItem, addition: {})
CommandGroup(after: .newItem, addition: {
an existing one. For instance, the system includes an option in the File Button("Option 1") {
menu called New Window. This option is represented by the newItem print("This is option 1")
}
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.
Figure 19-11: Standard option removed
Listing 19-6: Adding options to a standard menu

.commands {
CommandGroup(after: .newItem, addition: {
Button("Option 1") {
print("This is option 1") 
}
})
} We can also add submenus to a menu by using a Picker view instead of a

Button view. For this purpose, we need a property in the model to store the
current state. }
#endif
}
Listing 19-8: Defining a property in the model to store the index of the }

selected option

Figure 19-12: Submenu
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 Do It Yourself: Create a Swift file called ApplicationData.swift for the
@Published property in the model, so views know which option is currently model in Listing 19-8. Update the App structure with the code in
selected. Listing 19-9. Run the application on the Mac. Open the File menu
and select an option. The option should remain selected.
Listing 19-9: Adding a submenu

SwiftUI includes structures that add predefined commands to the menu
import SwiftUI
bar. The structures currently available are SidebarCommands,
TextEditingCommands, TextFormattingCommands, ToolbarCommands, and
@main
struct TestApp: App { ImportFromDevicesCommands. Probably the most interesting is
@StateObject var appData = ApplicationData() ImportFromDevicesCommands, which adds a submenu to allow the user to
import resources from nearby devices. For instance, we can load and
var body: some Scene {
WindowGroup { process an image in our Mac application that is taken by the camera of an
ContentView() iPhone.
.environmentObject(appData)
} The option is added by the structure, but to process the data, we must
#if os(macOS) apply the following modifier to a view.
.commands {
CommandGroup(after: .newItem, addition: {
Picker("Options", selection: $appData.selectedOption) { importableFromServices(for: Type, action: Closure)—This
Text("Option 1").tag(1)
Text("Option 2").tag(2) modifier imports data of the type specified by the first argument. The
Text("Option 3").tag(3)
}
action argument provides a closure to process the data.
})

To add one of these menu options, all we need to do is to include an import SwiftUI
instance of the structure in the closure assigned to the commands() modifier.
The following example adds the option to import resources from external #if os(macOS)
devices. struct ImageRepresentation: Transferable {
let image: NSImage
Listing 19-10: Adding predefined options to import resources static var transferRepresentation: some TransferRepresentation {
 DataRepresentation(importedContentType: .jpeg, importing: { data in
.commands { if let newImage = NSImage(data: data) {
ImportFromDevicesCommands() return ImageRepresentation(image: newImage)
} } else {
 return ImageRepresentation(image: NSImage(named: "nopicture")!)
}
})
Figure 19-13: Option to import an image from iPhones or iPads }
}
#endif
appear on the screen. For more information on the Transferable struct ContentView: View {
@EnvironmentObject var appData: ApplicationData
protocol, see Drag and Drop Gesture in Chapter 12.
var body: some View {
VStack {
The options in a menu can be disabled. To disable an option, all we need to TextField("Insert your Name", text: $appData.inputMessage)
do is to apply the disabled() modifier to the Button view that represents the Spacer()
}.padding()
option and connect it with a state in the model that we can use to enable .frame(width: 500, height: 350)
or disable the option when a condition is met. For instance, we can disable }
the option when no text was inserted in a TextField view. To manage the }

value, we need a @Published property in the model.
When the user inserts a value in the text field, the option is enabled, but it
Listing 19-12: Defining a state to enable and disable a menu option is immediately disabled when the field is empty.

class ApplicationData: ObservableObject { Figure 19-14: Option disabled
@Published var inputMessage: String = ""
}

Now we can disable the option in the menu when this property is empty.
Do It Yourself: Update the ApplicationData class with the code in Listing @Published var inputMessage: String = ""
@Published var inputAddress: String = ""
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
In this example, we define a structure called AddressKey that conforms to
should be disabled. Insert a text in the text field and open the menu the FocusedValueKey protocol with a typealias of String called Value, so we can
again. Now the option should be enabled. store the values managed by the TextField view. Next, we define an
extension of the FocusedValues structure to include our own property. We call
In the last example, we disable the option if a condition is not met, but this property address. The data type is the Value type defined by the previous
often options are enabled or disabled depending on which element on the structure, but set as an optional. This is so the system can assign the value
interface is focused. For example, we may have two TextField views, but the nil to the property when the view is not focused. The property includes a
action can only be performed when the user is working on one of them setter and a getter to set and return the value in the collection with our
(the text field is focused). In Chapter 6, we have learned how to handle structure as the key.
focus changes in a view, but to pass the focus state from one view to Now we need to set and read this focus state from the view and the App
another, or as in this case, from a view to the menu bar, we need to structure. To set the focus state, SwiftUI includes the following modifier.
implement a structure called FocusedValues. This structure is a collection of
values managed by the system that contains the state of the focused view. focusedValue(WritableKeyPath, Value)—This modifier stores a
Each state is identified by a structure that conforms to the FocusedValueKey value in the FocusedValues structure. The first argument is the key path
protocol, which only requirement is a typealias with the name Value and the
of the property in the FocusedValues structure we want to use to store
data type of the value managed by the view we want to monitor. Once we
the value, and the second argument is the value we want to store
have this structure, we need to add a property of this type to the
(usually the view's state).
FocusedValues structure with an extension, as in the following example.
To observe the value from the focused view, SwiftUI includes the
Listing 19-15: Storing a focus state in the FocusedValues structure
@FocusedValue property wrapper. This property wrapper is created from the

FocusedValue structure, which includes the following initializer.
import SwiftUI
instead of a single view. For more information, visit our website and Toolbar
follow the links for this chapter. 
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.
Mac Modifiers })
.help("Press this button to add a book")
}
 }

SwiftUI defines a few modifiers that are exclusive for Mac applications. The
following are the most frequently used. 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".
help(String)—This modifier creates a tooltip. (A tooltip is a message
that appears next to the view when the mouse is positioned on top of Figure 19-16: Tooltip
it for a few seconds.)
collapsible(Bool)—This modifier determines if a section of a List
view of type sidebar can be collapsed by the user.
inset(alternatesRowBackgrounds: Bool)—This type method The collapsible() modifier and the inset() type method apply to List views. The
assigns the inset style to the List view. The alternatesRowBackgrounds collapsible() modifier is used to collapse sections in a sidebar list, while the
argument determines whether the views are going to be displayed inset() method returns a style for the list that highlights rows to make them
with alternate backgrounds. easy to identify.
To test these two features, we are going to create a small app that shows a
The help() modifier is simple. All it does is to show a label when the mouse list of items selected from a category. The following is the model with some
moves over the view and remains in that position for a few seconds. Its testing values.
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 Listing 19-21: Defining a model with data organized in sections
button added to the toolbar in the previous example. 
import SwiftUI
 }
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
This example applies the collapsible() modifier to the Section view, so the user DetailView(items: ConsumableItems(name: "", items: []))
can collapse or expand each section on the list. Notice that for the .environmentObject(ApplicationData())
}
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 Figure 19-17: Highlighted rows and collapsable sections
ConsumableItems structure is passed to the DetailView view and the values are
displayed on the list, as shown next.
The Window structure creates a single independent Scene, and therefore it Do It Yourself: Create a Multiplatform project. Update the App
is useful for applications that cannot allow the user to open multiple structure with the code in Listing 19-25. Create a new SwiftUI View
windows, like video games, but can also be composed together with other file called AuxiliaryView.swift with a single Text view. Run the
Scenes to present an auxiliary window, as in the following example. application on the Mac. Open the Window menu. You should see an
option with the name of the window, as shown in Figure 19-18 (My
Listing 19-25: Opening an auxiliary window Window).

import SwiftUI The menu option is automatically added to the Window menu when we
include a Window Scene in the App structure, but most users don't know that
@main
struct TestApp: App { the option even exists. To provide a better alternative, the Environment
var body: some Scene {
WindowGroup {
includes the following property to open the window programmatically.
ContentView()
}
@main
@main

struct TestApp: App {
var body: some Scene {
WindowGroup {
To show a view instead of a menu, we must apply the menuBarExtraStyle()
ContentView() modifier with the window value and replace the buttons with a single view
}
#if os(macOS)
and its content. Scene Storage

Listing 19-30: Defining a control to open a view

iPads and Mac computers can open multiple instances of an application in
MenuBarExtra("My Menu", systemImage: "phone") {
VStack { separate windows (Scenes). iPads offer multiple alternatives to open the
HStack { app in a new window. The easiest way to do it is to split the screen with
Spacer()
Button(action: {
the bottoms at the top and open the app again (see Chapter 12, Figure 12-
NSApplication.shared.terminate(nil) 4). On the Mac is even easier, the option is available on the File menu.
}, label: {
Image(systemName: "xmark.circle")
}) Figure 19-23: Option to create a new window
}.padding()
Button("Option 1") {
print("Option 1")
}.buttonStyle(.borderedProminent)
Button("Option 2") {
print("Option 2")
}.buttonStyle(.borderedProminent) 
Spacer()
}.frame(width: 200, height: 180)
}.menuBarExtraStyle(.window)
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
Figure 19-22: Control with a view 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.

import SwiftUI Every time the user selects a different value, the Picker view stores that
value in the selection property and, therefore, the value is preserved. If we
class ApplicationData: ObservableObject {
@Published var picturesList: [String] close and open the app again, the initial value will be the one we selected
before the app was closed. And because we are using the @SceneStorage
init() {
picturesList = ["bagels", "brownies", "butter", "cheese", "coffee", "cookies", "donuts", "granola", property to set the Picker view, the selection is unique for each Scene.
"juice", "lemonade", "lettuce", "milk", "oatmeal", "potato", "tomato", "yogurt"]
}
} Figure 19-24: Scene with custom values

The interface must include a Picker view to select the picture and an Image
view to show the selected picture on the screen.
At the beginning of this book, we talked about Apple’s strict control over Developing and testing can be done with a free account, but publishing our
the applications users can access. Applications for Mac computers can be app requires a membership to the Apple Developer Program. The option to
sold separately, but mobile applications can only be sold in the App Store. enroll in this program is available on the developer.apple.com website. We
The tools to submit our application to the App Store are provided by must click on the Discover/Program options at the top of the screen, press
Xcode, but there are a series of requirements we need to satisfy for our the Enroll button, and follow the instructions to register an account for an
app to be published and become available to users. Individual or an organization. At the time of writing, the membership costs
USD 99 per year.
We need an Apple Developer Program membership.
We need a Distribution Certificate.
We need a Provisioning Profile for distribution.
We need an App ID to identify the application.
We must register the app in the App Store Connect website.
We must create an archive with our app for each platform to send
to Apple servers.
We must upload the archive to App Store Connect for review.
Certificates, Provisioning Profiles, and Identifiers right panel shows the list of values available and buttons to create new
ones. When a value is selected, a new panel opens with tools to edit it.

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 The launch screen is the first screen the user sees when the app is
resources Apple needs to make our app available to the public. An launched. No matter the size, applications always take a few seconds to
important resource we must include in our project are the app's icons. load. The launch screen is required to give the user the impression that the
Icons are the little images that the user taps or clicks to launch the app. By app is responsive.
default, the Asset Catalog includes a set called AppIcon to manage the We can define two aspects of the launch screen: the background color and
icons for the application. The set includes placeholders for every icon we an image. The values are specified from the Info panel in the app's settings.
need and for every scale and device available. The option is called Launch Screen. When we press the + button on this
Icons may be created with any image editing software available on the key, Xcode shows a list of options to choose from.
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 Figure 20-3: Launch Screen options
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).
There are two options available to define the content and four to configure
Figure 20-2: AppIcon set with the icons for iPhones and iPads
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


The first step to submit our application is to create a record on Apple’s
Apple’s guidelines recommend creating a launch screen that closely servers. Apple has designated a website for this purpose, available at
resembles the first screen of your app. For instance, if the app's interface appstoreconnect.apple.com. To login, we must use the same Apple ID and
background is yellow, we should assign to the launch screen a yellow password we use to access our account at developer.apple.com. Figure 20-
background as well. In our example, we define two sets in the Asset 6 illustrates the options available after logging in.
Catalog. The launchLogo set includes a PNG image with a logo, and the
launchColor set defines a yellow color for the light and dark appearances. Figure 20-6: App Store Connect menu
The result is shown below.
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 Submitting the Application
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 application and resources must be compiled for each platform in a
the app later. The name and language are values we already have, and the
single archive and then submitted to App Store Connect. We must create
SKU is a custom string, but the Bundle ID is a value generate by Xcode.
an archive for iOS devices and another for macOS. The option is available
Xcode creates a Bundle ID and submits it to Apple servers when we enable
on the Xcode’s Product menu, but it is only enabled when the appropriate
services from the capabilities panel. If our app does not use any of these
device is selected from the Schemes. We can select a real device
services, we can register a new Bundle ID from developer.apple.com.
connected to the computer, or we can use the Any iOS Device option for
iOS apps or the Any Mac option for macOS (Figure 20-9, number 1).
Figure 20-8: Bundle ID and SKU identifier


After these values are inserted, we can press the Create button and
complete the rest of the information. This includes the app’s description,
After we click on the Archive option (Figure 20-9, number 2), Xcode
screenshots, and personal information. We also must select the option
compiles the application and creates the archive. The next window shows
Pricing and Availability on the left panel to set the price and where the
the archive and offers buttons to validate and submit the app.
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.
Figure 20-10: List of archives created for our app
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.
After the archive is selected, we can press the Save button to save the Find more books at
app’s description. If all the required information was provided, we can www.formasterminds.com
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 J.D Gauchat
is sent to Apple for review (the message Waiting for Review is shown www.jdgauchat.com
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.