0% found this document useful (0 votes)
790 views411 pages

UIKit For Masterminds 4page

Uploaded by

edwin
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
790 views411 pages

UIKit For Masterminds 4page

Uploaded by

edwin
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 411

UIKit for Masterminds

Copyright © 2021 John D Gauchat


UIKit All Rights Reserved

for Masterminds No part of this publication may be reproduced, distributed, or transmitted


How to take advantage of Swift and UIKit in any form or by any means, including photocopying, recording, or other
to create insanely great apps for electronic or mechanical methods, without the prior written permission of
the publisher, except in the case of brief quotations embodied in critical
iPhones, iPads, and Macs
reviews and certain other noncommercial uses permitted by copyright law.

Companies, services, or product names used in this book are for


J.D Gauchat identification purposes only. All trademarks and registered trademarks are
www.jdgauchat.com the property of their respective owners.

Apple™, iPhone™, iPad™, Mac™, among others mentioned in this work, are
trademarks of Apple Inc.

The information in this book is distributed without warranty. Although


every precaution has been taken in the preparation of this work, neither
the author nor the publisher shall have any liability to any person or entity
with respect to any loss or damage caused or alleged to be caused directly
or indirectly by the information contained in this work.

The source code for this book is available at


www.formasterminds.com
Table of Contents
Copyright Registration Number: 1185933
CHAPTER 1 - APP DEVELOPMENT
1.1 OVERVIEW
1st Edition 2021
REQUIREMENTS
1.2 XCODE
1.3 DEVELOPMENT
PROGRAMMING LANGUAGES
FRAMEWORKS AND APIS
COMPILER

CHAPTER 2 - INTRODUCTION TO SWIFT


2.1 COMPUTER PROGRAMMING
PLAYGROUND
2.2 VARIABLES
MEMORY
PRIMITIVE DATA TYPES
DECLARATION AND INITIALIZATION
ARITHMETIC OPERATORS
CONSTANTS
2.3 DATA TYPES
CHARACTERS
STRINGS
BOOLEANS
OPTIONALS
TUPLES
2.4 CONDITIONALS AND LOOPS
IF AND ELSE
SWITCH
WHILE AND REPEAT WHILE
FOR IN
CONTROL TRANSFER STATEMENTS
GUARD SELF
MEMORY MANAGEMENT
CHAPTER 3 - SWIFT PARADIGM INHERITANCE
3.1 PROGRAMMING PARADIGMS
TYPE CASTING
3.2 FUNCTIONS
INITIALIZATION
DECLARATION OF FUNCTIONS
DEINITIALIZATION
GENERIC FUNCTIONS
ACCESS CONTROL AND MODIFIERS
STANDARD FUNCTIONS
3.6 PROTOCOLS
SCOPES
DEFINITION OF PROTOCOLS
CLOSURES
GENERIC PROTOCOLS
3.3 STRUCTURES
SWIFT PROTOCOLS
DEFINITION OF STRUCTURES
EXTENSIONS
KEY PATHS
DELEGATES
METHODS
3.7 ERRORS
INITIALIZATION
THROWING ERRORS
COMPUTED PROPERTIES
HANDLING ERRORS
PROPERTY OBSERVERS
RESULTS
TYPE PROPERTIES AND METHODS
GENERIC STRUCTURES CHAPTER 4 - INTRODUCTION TO FRAMEWORKS
PRIMITIVE TYPE STRUCTURES 4.1 FRAMEWORKS
RANGE STRUCTURES IMPORTING FRAMEWORKS
STRING STRUCTURES 4.2 FOUNDATION
ARRAY STRUCTURES MORE STANDARD FUNCTIONS
SET STRUCTURES STRINGS
DICTIONARY STRUCTURES RANGES
3.4 ENUMERATIONS NUMBERS
RAW VALUES DATES
ASSOCIATED VALUES MEASUREMENTS
COLLECTION DIFFERENCE TIMER
3.5 OBJECTS 4.3 CORE GRAPHICS
DEFINITION OF OBJECTS DATA TYPES
TYPE PROPERTIES AND METHODS
REFERENCE TYPES
CHAPTER 5 - UIKIT FRAMEWORK

5.1 UIKIT TEXT VIEW


APPLICATION KEYBOARD
DEVICE PICKER VIEW
WINDOWS DATE PICKER
VIEWS 5.7 IMAGES
UIVIEW SUBCLASSES ASSETS CATALOG
SCENES IMAGE
5.2 XCODE SF SYMBOLS
PROJECTS IMAGE VIEW
TOOLS PROCESSING IMAGES
RUNNING THE APP VISUAL EFFECTS
5.3 TEMPLATES DARK APPEARANCE
MVC ICONS
APPLICATION DELEGATE ACCENT COLOR
SCENE DELEGATE LAUNCHING IMAGE
STORYBOARD
VIEW CONTROLLERS
CHAPTER 6 - ADAPTIVITY
6.1 ADAPTIVITY
5.4 VIEWS
6.2 AUTO LAYOUT
LIBRARY
CONSTRAINTS
GUIDE LINES
ASSIGNING CONSTRAINTS
OUTLETS
EDITING CONSTRAINTS
DOCUMENT OUTLINE PANEL
SAFE AREA
LABELS
RESOLVING AUTO LAYOUT ISSUES
ACTIONS
INTRINSIC CONTENT SIZE
BUTTONS
MULTIPLE VIEWS CONSTRAINTS
OUTLET COLLECTIONS
RELATIONS AND PRIORITIES
PROGRESS VIEW
STACK VIEWS
ACTIVITY INDICATOR
CONSTRAINT OBJECTS
SEGMENTED CONTROL
UPDATING FRAMES
SWITCH
6.3 SIZE CLASSES
SLIDER
ADAPTING PROPERTIES
STEPPER
ADAPTING CONSTRAINTS
TEXT FIELD
ADAPTING ELEMENTS 10.1 TABLES
TRAIT COLLECTION TABLE VIEWS
ORIENTATION TABLE VIEW CELLS
DATA SOURCE
CHAPTER 7 - SCROLL VIEWS INDEX PATHS AND IDENTIFIERS
7.1 SCROLL VIEWS IMPLEMENTING TABLE VIEWS
IMPLEMENTING SCROLL VIEWS BACKGROUND
CUSTOMIZING SCROLL VIEWS STATES
ZOOMING CELL SUBCLASS
PAGES CUSTOM CELL
SCROLLING THE INTERFACE TABLE VIEW DELEGATES
CHAPTER 8 - GESTURE RECOGNIZERS 10.2 TABLE VIEWS IN NAVIGATION CONTROLLERS
8.1 GESTURES ADDING ROWS
DELETING ROWS
CHAPTER 9 - NAVIGATION MOVING ROWS
9.1 MULTIPLE VIEW CONTROLLERS MODIFYING ROWS
SEGUES 10.3 TABLE VIEW CONTROLLER
UNWIND SEGUES REFRESH CONTROL
SEGUES IN CODE 10.4 SEARCH
9.2 NAVIGATION CONTROLLERS SEARCH BAR
NAVIGATION CONTROLLERS IN THE STORYBOARD 10.5 SECTIONS
NAVIGATION BAR DELETING SECTIONS
LARGE TITLES 10.6 STATIC TABLES
BAR BUTTONS
TOOLBAR CHAPTER 11 - COLLECTION VIEWS
CUSTOM NAVIGATION 11.1 COLLECTION VIEWS
DATA MODEL COLLECTION VIEW CELLS
9.3 TAB BAR CONTROLLERS COLLECTION VIEW DELEGATE
TABS DATA SOURCE
TAB BAR CONTROLLER 11.2 IMPLEMENTING COLLECTION VIEWS
TAB BAR CONTROLLER DELEGATE FLOW LAYOUT
CUSTOM LAYOUT
CHAPTER 10 - TABLE VIEWS SUPPLEMENTARY VIEWS

SECTIONS TASK GROUP


11.3 LISTS
CHAPTER 15 - STORAGE
CHAPTER 12 - SPLIT VIEW CONTROLLERS 15.1 USER PREFERENCES
12.1 UNIVERSAL CONTAINER USER DEFAULTS
SPLIT VIEW CONTROLLER 15.2 FILES
SPLIT VIEW CONTROLLER CONFIGURATION URLS AND PATHS
SPLIT VIEW CONTROLLER DELEGATE FILES AND DIRECTORIES
12.2 IMPLEMENTING SPLIT VIEW CONTROLLERS FILES ATTRIBUTES
SWIPE GESTURE FILES CONTENT
DISPLAY MODE BUNDLE
DEFAULT ITEM 15.3 ARCHIVING
DEFAULT COLUMN ENCODING AND DECODING
COMPACT SCENE 15.4 CORE DATA
THREE-COLUMN DESIGN DATA MODEL
SECONDARY ONLY BUTTON CORE DATA STACK
12.3 MODAL SCENES MANAGED OBJECTS
PRESENTATION CONTROLLER ASYNCHRONOUS ACCESS
POPOVER PRESENTATION CONTROLLER STORING OBJECTS
COUNTING OBJECTS
CHAPTER 13 - ALERT VIEWS PREDICATES
13.1 ALERT VIEWS SORT DESCRIPTORS
ALERTS MODIFYING OBJECTS
ACTION SHEETS DELETING OBJECTS
CHAPTER 14 - CONCURRENCY FETCHED RESULTS CONTROLLER
14.1 ASYNCHRONOUS AND CONCURRENT TASKS SEARCH
TASKS SECTIONS
ASYNC AND AWAIT TO-MANY RELATIONSHIPS
ERRORS CHAPTER 16 - NOTIFICATIONS
CONCURRENCY 16.1 NOTIFICATION CENTER
ACTORS SYSTEM NOTIFICATIONS
MAIN ACTOR 16.2 USER NOTIFICATIONS
ASYNCHRONOUS SEQUENCES
USER NOTIFICATIONS FRAMEWORK ERRORS
MEDIA ATTACHMENTS CLOUDKIT AND CORE DATA
PROVISIONAL NOTIFICATIONS DEPLOY TO PRODUCTION
NOTIFICATIONS DELEGATE
GROUPS
CHAPTER 18 - MEDIA
18.1 MEDIA
SUMMARY
CAMERA
ACTIONS
STORING PICTURES
16.3 KEY/VALUE OBSERVING
PHOTO LIBRARY
KVC
CUSTOM CAMERA
KVO
AVKIT FRAMEWORK
CHAPTER 17 - ICLOUD CUSTOM VIDEO PLAYER
17.1 DATA IN THE CLOUD 18.2 COLOR PICKER
ENABLING ICLOUD
TESTING DEVICES
CHAPTER 19 - WEB
17.2 KEY-VALUE STORAGE 19.1 LINKS
17.3 ICLOUD DOCUMENTS 19.2 SAFARI VIEW CONTROLLER
DOCUMENTS 19.3 WEBKIT FRAMEWORK
METADATA QUERY APP TRANSPORT SECURITY
SINGLE DOCUMENT 19.4 WEB CONTENT
MULTIPLE DOCUMENTS JSON
17.4 CLOUDKIT CHAPTER 20 - MAP KIT
CONTAINER 20.1 MAP KIT VIEW
RECORDS CONFIGURING THE MAP
ZONES ANNOTATIONS
QUERY USER LOCATION
OPERATIONS SEARCH
BATCH OPERATIONS DIRECTIONS
REFERENCES
CLOUDKIT DASHBOARD CHAPTER 21 - DRAG AND DROP
IMPLEMENTING CLOUDKIT 21.1 DRAG AND DROP
ASSETS DRAG
SUBSCRIPTIONS DROP

LISTS
Conventions
CHAPTER 22 - MAC CATALYST
22.1 MAC CATALYST This book explores basic and advanced topics required to develop
MAC APPS professional applications. Depending on your current level of knowledge,
CONDITIONAL CODE you may find some of these topics easy or difficult to learn. To help you
MENU navigate the book, we have identified each section with a label. The
TOOLBAR following is a description of what these labels represent.
GESTURES
VIEWS

22.2 MULTIPLE WINDOWS
The Basic label represents topics you can ignore if you already know the
CHAPTER 23 - APP STORE basics of Swift and app development. If you are learning how to develop
23.1 PUBLISHING applications for Apple devices for the first time, these sections are
APPLE DEVELOPER PROGRAM required.
CERTIFICATES, PROVISIONING PROFILES, AND IDENTIFIERS
APP STORE CONNECT 
SUBMITTING THE APPLICATION 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 when you need it later 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 not always represent 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.

IMPORTANT: Apple technologies are extensive, and a book cannot


teach you everything. After each topic is introduced, you should read
the official specifications provided by Apple and look for additional
examples on the Web. The links to the specifications, additional
information, tutorials, and videos are available on our website
www.formasterminds.com. Apple's official documentation is
available at developer.apple.com. Frameworks and APIs references
are available at developer.apple.com/reference.

1.1 Overview Requirements


 

After the introduction of the iPhone in 2007 and its massive success, Apple Apple requires developers to use the software provided by the company to
decided to provide the tools for developers to create applications for it. create apps for its devices, and this software only works on Apple
The tools were already available for Mac computers, but the iPhone's small computers. For this reason, the options are limited, but the good news is
screen and unique characteristics called for a different approach. So, along that the tools and accounts we need are provided by the company for free.
with those already available, Apple introduced a specific set of tools to
Mac Computer—This in theory could be any Mac computer, but the
develop apps for these devices called UIKit.
development software always requires the latest operative system
UIKit (User Interface Kit) includes everything a developer needs to manage
(currently macOS Monterey), so in practice we need a relatively new
a device and create the elements of the graphic interface. It was designed computer with a recommended 16GB of memory.
to develop apps for iPhones and iPads, but thanks to a new system called
Xcode—This is the software provided by Apple for development. The
Mac Catalyst, we can write applications for Mac computers, as well.
latest version is number 13. It’s free and the package comes with
everything we need to create our apps, including an editor, the SDK
(Software Development Kit), and a simulator to test the applications.
Apple Developer Account—This is a basic account we can get for
free. From this account, we can manage our membership, create
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 operative
systems (currently iOS 15, tvOS 15, watchOS 8, iPadOS 15, and macOS
Monterey). Testing our applications on a real device is highly
recommended and necessary before publishing.

In short, to develop applications for Apple devices we need a Mac


Computer capable of running the operative system required by the latest
version of Xcode (currently macOS Monterey), make sure that we have an 1.2 Xcode
Apple ID to access our Developer Account (developer.apple.com), and

install the latest version of Xcode (currently 13).

Xcode is a general-purpose IDE (Integrated Development Environment). It


includes a very powerful editor with graphic tools to help us write our
code, the SDKs (Software Development Kits) for the creation of software
for the iOS, iPadOS, macOS, watchOS, and tvOS operative systems, and
compilers for the C, C++, Objective-C and Swift languages. From Xcode, we
can program software for every Apple platform using any of these tools
and programming languages.
Xcode is available as an app in the Mac App Store. To download this
application, we must open the App Store from Launchpad (the application
organizer that comes with macOS) or double click the App Store icon inside
the Applications folder in Finder (macOS’s file explorer).
If we search for the term "Xcode" in the App Store, the window shows
Xcode’s icon at the top and a button to download it (Figure 1-1, number 1).

Figure 1-1: Xcode in the Mac App Store

Once the application is downloaded, the software is automatically


installed. To start Xcode, we open Launchpad and click on the icon or
double-click the program from the Applications folder in Finder. Figure 1-2
shows Xcode's welcome screen.

Figure 1-2: Xcode's welcome screen

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 the 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, 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 critical for 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 following
chapters.

Compiler

Computers do not understand Swift or any other programming language.


These languages were created for us to give machines instructions we can
understand. Our code must be converted to elemental orders that work at
an electronic level, turning multiple switches on and off to represent the
abstraction humans work with. The translation from the language humans
understand to the language computers understand is done by a program
called compiler.
Compilers have specific routines to translate instructions from
programming languages to machine code. They are language and platform
specific, which means that we need a specific compiler to program in one
language and platform. There are a few compilers available for Apple
systems, but the one currently implemented by Xcode is called LLVM. LLVM
is capable of compiling code written in Swift, C, C++, and Objective-C. 
With the compiler, the machinery to build an app is complete. Figure 1-3
shows all the elements involved. There are three main sources of code the IMPORTANT: The Application Loop is a group of elemental
compiler uses to build the application: our code in Swift, the frameworks routines, common to every program, that connects your app to the
our program requires, and a set of basic routines necessary for the app to operative system and provides a loop (a code that executes itself
run (called Application Loop in Figure 1-3). The process starts from Xcode. over and over again) to constantly check for events produced by the
In this program we write our code, access frameworks through their APIs, user or coming from the system. Although you never work directly
and configure the app to be compiled (built). Combining our code, the with these routines, they are connected to your code to inform the
codes from the frameworks our app requires and the basic routines state of the program, as we will see in further chapters.
(Application Loop), the compiler creates an executable program that may
be run in a simulator, a device, or submitted to the App Store for
distribution.

Figure 1-3: Building an App


CHAPTER 2 - INTRODUCTION TO SWIFT 2.1 Computer Programming

Computers can't do anything unless we write a program. A program is a


succession of instructions that the computer must follow. We write the
program using the instructions provided by a specific programming
language, then a compiler translates these instructions into orders the
computer can understand, and when we tell the computer to run the
program, the orders are executed sequentially one by one.
The instructions are always listed in sequential order, but programming
languages offer different ways to group them together and organize the
code and the data that is going to be processed. Developing an app
demands a deep understanding of these instructions and the combinations
required to achieve the results we want. Since this may be daunting for
beginners, Xcode includes a tool called Playground to learn how to
program and test our code.

Playground

As the name suggests, Playground offers a place to experiment and play


around with our code before including it in our applications. Although we
could start an Xcode project to create an application right away, it is better
to work with Playground first to learn how to program and how to take
advantage of some of the fundamental frameworks included in the SDK.
Playground files are created from the Playground… option in the File menu 
at the top of the screen.
After we select the template, Xcode asks for the name of the Playground
Figure 2-1: Playground option file and the place on our hard drive where we want to store it. Once the
file is created, Xcode shows the Playground's interface on the screen.
Figure 2-3 illustrates what we see when we create a Blank template.

Figure 2-3: Playground's interface

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
Playground presents a simple interface with a toolbar at the top and four
program a video game), Map (with the code to display a map), and Single
View (with the same code required to create a view for an application). areas. The Navigator Area where we can see the resources included in our
Playground project, the Editor Area where we write our code, the Results
Figure 2-2: Playground's templates Side Bar on the right where the results produced by our code are
displayed, and the Console at the bottom where we can read the errors
produced by the code and print our own messages. The interface includes var greeting = "Hello, playground"

buttons to open or remove some of these panels. The button on the top-
left corner removes the Navigator Area (number 1), the one on the top-
right corner removes a panel called Utilities Area with information about A computer program is just text written in a specific format. Each line of
the selected resource, and the button at the top-left corner of the Console text represents an instruction. Sometimes a single line includes several
area removes the console. instructions, and therefore each line is usually called statement. Every
As illustrated in Figure 2-3, the Editor Area includes a button at the bottom statement is an order, or a group of orders, required for the computer to
of the panel to run and stop the code (Play Button). There is also a play perform a task. In the code of Listing 2-1, the first statement uses the
button on the left side of the Editor Area that we can press if we want to instruction import to include in our code the pre-programmed codes from
execute parts of the code instead (number 4). When this button is pressed, the UIKit framework, and the second statement uses the instruction var to
the code is executed up to the line in which the button is located. store the text "Hello, playground" in memory.
Playground can run the code automatically or wait until we press the Play If we press the Play Button to execute the code, we see what the code
button. By default, the mode is set to Automatically Run, but we can press
does inside the Results Side Bar (in this case, it shows the text stored in
and hold the Play button to access the menu to modify this behavior.
memory by the var instruction). When we move the mouse over this
Figure 2-4: Playground's running mode indication, two small buttons show up, as illustrated below.

Figure 2-5: Quick Look and Show Result buttons

 

In the Editor Area, we can see the code we have programmed so far. When The button on the left is called Quick Look, and it shows a popup window
a new Playground file is created, Xcode offers a template that includes a with a visual representation of the result of the execution of the code, such
few basic lines of code to start with. Listing 2-1, below, is the code
as the formatted text or an image. In this case, no visual effect is generated
currently generated for the Blank template.
by the code, so we only see the plain text.
Listing 2-1: Playground template

Figure 2-6: Quick Look window
import UIKit

2.2 Variables

Variables are names representing values stored in memory. Once a variable



is defined, its name remains the same but the value in memory they
represent may change. This allows us to store and retrieve a value from
The button on the right is called Show Result, and what it does is to open a memory without the need of remembering where in the memory the value
window within our code with a visual representation of the results of the was stored. With just mentioning the name of the variable we used to
execution of the code over time. In this case, nothing changes, so only the store the value, we can get it back or replace it with a new one. With
"Hello, playground" text is shown. variables, the system takes care of managing the memory for us, but we
still need to know how memory works to find out what kind of values we
Figure 2-7: Result window 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 An on switch represents the value 1 and an off switch represents the value
 0. Basic units were determined with the purpose of identifying parts of this
endless series of digits. One cell was called a bit and a group of 8 bits was
The computer’s memory is like a huge honeycomb, with consecutive cells called a byte. Figure 2-10 shows how a byte looks like in memory, with
that can be in two possible states: activated or deactivated. They are some of its switches on representing the binary number 00011101.
electronic switches with on and off positions established by low and high
energy levels. Figure 2-10: Representation of one byte in memory

Figure 2-8: Memory cells

 

Because of their two possible states, each cell is a small unit of The possible combinations of 8 bits are 256, therefore, a byte can
information. One cell may represent two possible states (switch on or off), represent binary numbers of 8 digits, which in turn can be converted to
but by combining a sequence of cells we can represent more states. For numbers that humans can understand, such as decimal numbers. With its
example, if we combine two cells, we have four possible states. 256 possible combinations, a byte can represent decimal numbers from 0
to 255. For instance, when the value of the byte in the example of Figure 2-
Figure 2-9: Combining two cells 10 is converted to the decimal system, we get the number 29 (00011101 =
29).

IMPORTANT: Numbers of one numeral system, like binary, can be


 converted to any other numeral system, like decimal. The binary
system is the one a computer can understand because it translates
With these two cells, we can now represent up to four states (4 possible directly to the electronic switches they are built with, but humans
combinations). If we had used three cells instead, then the possible find this difficult to read, so we use other systems to express
combinations would have been 8 (eight states). The number of numbers, like the decimal system.
combinations doubles every time we add another cell to the group. This
can be extended to represent any number of states we want. To represent larger numbers, bytes are grouped together. For example, if
Because of its characteristics, this system of switches is used to represent
we take two bytes from memory, we get a binary number composed of a
binary numbers, which are numbers expressed by only two digits: 0 and 1.
total of 16 bits (16 zeros and ones). A binary number of 16 bits can

represent decimal numbers from 0 to 65535 (a total of 65536 possible Primitive Data Types
combinations). To establish clearly defined data structures, each 
programming language declares its own units of data of a predetermined
size. These units are usually called primitive types. Primitive data types are units of data defined by the language. They are
always the same size, so when we store a value of one of these data types,
the computer knows exactly how much memory to use. The following is
probably the most useful primitive data type provided by Swift.

Int—This data type defines integer numbers, which are numbers with
no fractional component. In 64 bits systems, the size of this data type
is 8 bytes and therefore it can store values from
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

Although it is recommended to use the Int data type to store integers,


some frameworks require a very specific type of integer. For this reason,
Swift also defines the following data types.

Int8—This data type defines integer numbers of a size of 1 byte (8


bits). Because of its size, it can store values from -128 to 127.
Int16—This data type defines integer numbers of a size of 2 bytes (16
bits). Because of its size, it can store values from -32,768 to 32,767.
Int32—This data type defines integer numbers of a size of 4 bytes (32
bits). Because of its size, it can store values from -2,147,483,648 to
2,147,483,647.
Int64—This data type defines integer numbers of a size of 8 bytes (64
bits). Because of its size, it can store values from
-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.

If we check the size of each type presented so far and calculate the
possible combinations of bits, we will discover that the maximum values
don't match. For example, an Int8 uses 1 byte, which means it is composed Doublewhen performing calculations and use Float for minor tasks such as
of 8 bits, and for this reason it should be able to store numbers from 0 to storing coordinates to position graphics on the screen.
255 (256 possible combinations). The reason why an Int8 has a positive
limit of 127 is because it only uses 7 bits to store the value, the first bit on
the left is reserved to indicate the sign (positive or negative). Although
these limits are not restrictive, the language also provides the unsigned
versions of these types in case we need to store larger positive values.

UInt—This is the same as Int but for unsigned values. Because it does
not reserve a bit for the sign, in 64-bit systems it can store values from
0 to 18,446,744,073,709,551,615.

The specific data types for UInt are UInt8, UInt16, UInt32, and UInt64. These
data types work exactly like the equivalents for Int, but they are intended
to store only positive numbers.
Although all these data types are very useful, they are only good for storing
binary values that can be used to represent integer numbers. Arithmetic
operations also require the use of real numbers (e.g., 3.14 or 10.543).
Computers cannot reproduce these types of values, but they can work with
an approximation called floating-point numbers. The following are the
most frequently used floating-point data types defined in the Swift
language.

Float—This data type defines 32 bits floating-point numbers with a


precision of 6 digits.
Double—This data type defines 64 bits floating-point numbers with a
precision of at least 15 digits.

Floating-point types can handle large numbers using scientific notation, but
because of their precision, it is recommended to declare a variable of type

Declaration and Initialization variable we must always store a value in it to clear the space. This action is
 called Initialization.

Listing 2-3: Initializing variables


If we want to store data in memory, the first thing we need to do is to

select the right type from the data types provided by the language and
var mynumber: Int
then create a variable of that type. This action is called Declaration, and it mynumber = 5
is done using the var instruction and the syntax var name: type. 

Listing 2-2: Declaring variables


In the new example of Listing 2-3, we first declare the variable as we did

before, and then initialize it with the value 5 (we store the number 5 in the
var mynumber: Int
space of memory reserved for this variable). To store the value, we use the

= (equal) symbol and the syntax name = value, where name is the name of the
variable and value is the value we want to store (once the variable was
This example creates a variable called mynumber of type Int. When the
declared, we do not have to use the var instruction or specify its type
system reads this statement, it reserves a space in memory of 8 bytes long
anymore).
(64 bits) and assigns the name mynumber to that space. After the execution
Most of the time, we know what the variable’s initial value will be right
of this statement, we can use the variable mynumber to store in memory any
away. In cases like this, Swift allows us to declare and initialize the variable
integer value from -9,223,372,036,854,775,808 to in just one line of code.
9,223,372,036,854,775,807.
Listing 2-4: Declaring and initializing variables in the same statement
IMPORTANT: You can use any character to declare the name of a 
variable, except for spaces, mathematical symbols, and some var mynumber: Int = 5
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 Do It Yourself: If you haven't done it yet, create a new Playground
make sure that the name doesn't match any reserve word. If you file with the Blank template. Replace all the statements of the
declare a variable with an illegal name, Xcode will show you an error. Xcode’s template by the code in Listing 2-4 and press the Play
button. You should see the value 5 on the Results Side Bar. Repeat
The memory is a reusable resource. The space reserved for a variable may this process for the following examples.
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 point variable. When the values are of a clear data type, Swift can infer
change them any time we want. To store a new value in the space of them and the syntax may be simplified, as in the following example.
memory reserved for a variable, we must implement the same syntax used
for initialization. Listing 2-7: Declaring variables without specifying the type

Listing 2-5: Assigning a new value to a variable var mynumber = 5
 var myfavorite = 14.129
var mynumber: Int = 5 
mynumber = 87

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
The process of storing a new value is called assignment. In these terms, we 14.129 is clearly a floating-point value, so Swift creates the variable
can say that in the example of Listing 2-5 we "initialize the variable mynumber of type Int and the variable myfavorite of type Double (it selects the
mynumber with the number 5 and then assign the value 87 to it". The value most comprehensive type).
87 replaces the value 5 in memory. After that second statement is
executed, every time we read the mynumber variable from other statements IMPORTANT: Xcode offers a simple tool we can use to see the data
in the code it will return the value 87 (unless another value is assigned to
type assigned to a variable and get additional information. All you
the variable later).
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
IMPORTANT: Once a variable is declared, the values stored in that
variable must be of the same type. If we declare a variable of type of the variable, including its data type, and any information we may
Int, we cannot store floating-point values in it later (e.g., 14.129).
need to identify the code’s functionality. As we will see later, this not
only applies to variables but also to instructions, including methods
Of course, we can create all the variables we want and of any type we and properties.
need.
An important feature of variables is that the value of one may be assigned
Listing 2-6: Declaring variables of different types to another.

var mynumber: Int = 5 Listing 2-8: Assigning variables to variables
var myfavorite: Float = 14.129


var mynumber = 5
var myfavorite = mynumber
The first statement of Listing 2-6 declares an integer variable and initializes 
it with the value 5. The second statement does the same but for a floating-

The second statement in Listing 2-8 reads the value of the mynumber Arithmetic Operators
variable and assigns it to the myfavorite variable. The type of myfavorite is 
inferred to be Int (the same of mynumber). After this code is executed, we
have two integer variables, each with its own space in memory and the Storing values in memory is what variables allow us to do, but those values
value 5 stored in them. do not have to be declared explicitly, they can also be the result of
arithmetic operations. Swift supports the operations: + (addition), -
(subtraction), * (multiplication), / (division) and % (remainder).

Listing 2-9: Assigning the result of an operation to a variable



var mynumber = 5 + 10 // 15

IMPORTANT: The text added at the end of the statement of Listing


2-9 is a comment that we used to show the value produced by the
statement. Comments are ignored by the compiler but useful for
programmers to remember vital information. They are introduced
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
a comment and press the keys Command and /.

When the system reads the statement in Listing 2-9, it adds 10 to 5 and
assigns the result to mynumber (15). Of course, we can perform not only
addition but any operation we want.

Listing 2-10: Performing operations in variables of different type



var mynumber = 2 * 25 // 50
var anothernumber = 8 - 40 * 2 // -72
var myfraction = 5.0 / 2.0 // 2.5

The first two statements in Listing 2-10 are easy to read. They perform result that is expected to be an integer, any fractional part is discarded. In
arithmetic operations over integer numbers that produce an integer value this example, the system gets rid of the decimal 5 from the result and only
as a result, so the variables mynumber and anothernumber will be of type Int. A assigns the integer 2 to the variable. If we don't want to lose the fractional
problem arises when we work with operations that may produce floating- part, we must avoid inference and declare the data type ourselves as Float
point numbers. That is why in the third statement we specifically declared or Double.
the values as floating-point numbers (adding the fractional part .0). This Dividing integer numbers may be pointless most of the time, except in
forces Swift to infer the variable’s type as Double and produce a result of some circumstances when we need to know the remainder. The remainder
that type. is the amount left over by a division between two numbers and it is
When the compiler finds an operation with two or more numbers and has calculated using the % symbol.
to infer the type of the result, it converts the number of the less
Listing 2-12: Calculating the remainder
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
var remainder1 = 11 % 3 // 2
value is converted and processed as Double, and therefore the result will var remainder2 = 20 % 8 // 4
also be Double. var remainder3 = 5 % 2 // 1

Listing 2-11: Inferring the type from an operation
 Listing 2-12 shows three examples of how to calculate the remainder of a
var myfraction1 = 5.0 / 2.0 // 2.5 division. Each statement calculates the remainder of dividing the first
var myfraction2 = 5 / 2.0 // 2.5
var myfraction3 = 5 / 2 // 2 number by the second number and assigns the result to the variable. For
 instance, the first statement produces the remainder 2. The system divides
11 by 3 and finds a quotient of 3. Then, to get the remainder, it calculates
This example declares and initializes three variables. In the first statement 11 minus the multiplication of 3 times the quotient (11 – (3 * 3) = 2). The
both numbers were declared as floating-point values, so the compiler second statement produces a remainder of 4 and the third statement
infers a Double and creates the myfraction1 variable of that type. In the second produces a remainder of 1. This last statement is particularly useful
statement, we have an integer value and a floating-point value. Because of because it allows us to determine whether a value is odd or even. When
the floating-point value, the compiler interprets the integer (5) as a Double we calculate the reminder of an integer divided by 2, we get a result
(5.0) and creates the myfraction2 variable of type Double. But in the last according to its parity. If the number is even, the remainder is 0, and if the
statement there is no clear floating-point value. Both numbers were number is odd, the remainder is 1 (or -1).
declared as integers (with no decimals). In this case, the compiler does not Performing arithmetic operations becomes useful when instead of
know what we want to do, so it interprets both numbers as integers and numbers we use variables.
creates the myfraction3 variable of type Int. When an operation produces a

Listing 2-13: Adding numbers to variables -= is a shorthand for variable = variable - number, where number is the
 value we want to subtract from the variable’s current value.
var mynumber = 5
var total = mynumber + 10 // 15 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.

This example declares the mynumber variable and initializes it with the value Listing 2-15: Modifying the variable’s value using incremental operators
5. In the next statement, the total variable is declared and initialized with 

the result of the addition of the current value of mynumber plus 10 (5 + 10). var mynumber = 5
mynumber += 4 // 9
In Listing 2-13, we used a new variable to store the result of the operation,

but when the old value is not important anymore, we can store the result
back into the same variable.
The process generated by the code in Listing 2-15 is straightforward. After
Listing 2-14: Performing operations on the variable’s current value the value 5 is assigned to the mynumber variable, the system reads the
 second statement, gets the current value of the variable, adds 4 to that
var mynumber = 5 value, and stores the result back to mynumber (9).
mynumber = mynumber + 10 // 15

IMPORTANT: Swift also offers Overflow operators (&+, &-, &*, &/
and &%). These operators are useful when we think that an
In this example, the current value of mynumber is added to 10 and the result
operation could produce a result that goes over the limit the data
is assigned to the same variable. After the execution of the second
type can handle. For more information, visit our website and follow
statement, the value of mynumber is 15.
the links for this chapter.
Working with values previously stored in a variable allows our program to
evolve and adapt to new circumstances. For instance, we could add 1 to
the current value of a variable and store the result in the same variable to
create a counter. Every time the statement is executed, the value of the
variable is incremented by one unit. Recurrent increments and decrements
of the values of variables are very important in computer programming.
Because of this, Swift supports two operators that were specifically
designed for this purpose.

+= is a shorthand for variable = variable + number, where number is the


value we want to add to the variable’s current value.
Constants The mynumber constant declared in Listing 2-16 will always have the value 5.

IMPORTANT: When to use constants or variables depends on your
As mentioned before, the memory of a computer is a sequence of application. As guidance, you can follow what Apple recommends: if
switches. There are millions and millions of switches, one after another, a stored value in your code is not going to change, always declare it
with no clear delimitations. To be able to know where the space occupied as a constant with the let keyword. Use variables only for storing
by a variable starts and ends, the system uses addresses. These addresses values that need to be able to change.
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
be at the address 000001, the next one at 000002, and so on. If we declare
a variable of 4 bytes, the system reserves the four consecutive bytes and
remember where they are so as not to overwrite them with the value of
another variable. The task is easy when working with primitive types
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.
For example, the space in memory required to store the text "Hello" is
smaller than the space required for the text "Hello World". Managing the
memory for data of inconsistent sizes takes time and consumes more
resources than working with fixed sizes. This is one of the reasons why
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 manage the memory. To declare them, we must apply the same
syntax used for a variable but replace the var keyword by the let keyword.

Listing 2-16: Declaring and initializing a constant



let mynumber = 5

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.

2.3 Data Types Characters


 

Besides primitive data types, Swift defines additional data types to allow us Because of their nature, computers cannot store decimal numbers,
to work not only with numbers but also more complex values such as characters, or text. As we have seen in the previous section, the computer
logical values (true or false), characters, or text. memory is only capable of storing 1s and 0s (switches on and off), but they
can work with more complex values using tables that contain the
information necessary to represent those values (numbers, letters,
symbols, etc.). What the system stores in memory is not the character but
the value corresponding to the index of the character on the table. For
example, if we use the letter A, the value stored in memory will be the
decimal number 65 (in its binary representation) because that’s the
position of the letter A on the table used by Swift to define these
characters.
There are several standard tables of characters available. Swift is compliant
with a table called Unicode. This is a comprehensive table that includes
every character from almost any language in the world, and special
characters, such as emojis. Due to its broad range, the space in memory
required to store a single character varies from one character to another.
For this reason, Swift provides the Character type to store these values.

Listing 2-17: Declaring and initializing a Character variable



var myletter: Character = "A"

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.
Besides the characters we can type from the keyboard, Unicode allows us
to store emojis and symbols as characters, and Xcode offers a handy tool to
select the graphics we want. By pressing the combination of keys Control +
Command + Space, we can open a popup window and select a graphic with Strings
a click of the mouse.

Figure 2-11: Emojis and symbols
Individual characters are barely used in computer programming. Instead,
we usually store strings of characters. The String type was created for this
purpose.

Listing 2-18: Declaring and initializing a String variable



let mytext: String = "My name is John"

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
flexible; we may replace the string by another one of different length,

concatenate two or more, or even modify parts of them. Concatenation is
a common operation, and it is done with the + and += operators.

Listing 2-19: Concatenating strings



var mytext = "My name is "
mytext = mytext + "John" // "My name is John"

In Listing 2-19, the mytext variable is created with the value "My name is "
and then the string "John" is added at the end of the current value to get
the string "My name is John". The += operator works in a similar way, and
we can also combine them to get the string we want.

Listing 2-20: Concatenating strings with the + and += operators



let name = "John"
var mytext = "My name is "

mytext += name // "My name is John" 



let text1 = "This is \"my\" age" // "This is "my" age"
let text2 = #"This is "my" age"# // "This is "my" age"
With the + and += operators we can concatenate strings with strings. To 

concatenate strings with characters and numbers we must implement a


procedure called String Interpolation. The variables must be included Another important feature of strings is the possibility to create multiple
lines of text. Again, Swift offers two alternatives. We can include the special
inside the string between parentheses and prefixed by a backslash.
characters \n where we want to generate a new line or we can use triple
Listing 2-21: Including variables in strings quotes (""") and the compiler will consider the original format of the text
 and automatically insert the \n characters when required, as shown next.
let age = 44
let mytext = "I am \(age) years old" // "I am 44 years old" Listing 2-24: Generating multiple lines of text
 
let twolines = "This is the first line\nThis is the second line"
In this code, the age variable is read and its current value is added to the let multiline = """
This is the first line
string. The string "I am 44 years old" is then assigned to mytext. Using this This is the second line
tool, we can insert any value we want inside a string, including Character """

values, String variables, and arithmetic operations. In the following
example, the value of age is multiplied by 12 and the result is included in
The twolines constant defined in this example includes the \n characters in
the string.
the text, which tells the compiler to generate two lines of text. If we press
the Show Result button on the Results Side Bar, Xcode introduces a box
Listing 2-22: Performing operations inside strings
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,
let age = 44
let mytext = "I am \(age * 12) months old" // "I am 528 months old" although in this case the """ characters tell the compiler to add the \n
 characters at the end of each line for us.

Sometimes we need to include special characters in the string, like


backslashes or quotes. Swift offers two ways to achieve this purpose. We
can prefix the special character with another backslash, or we can enclose
the entire string in hash characters, as shown next.

Listing 2-23: Including special characters in a string


Booleans Optionals
 

Boolean variables are a type of variables that can only store two values: As mentioned at the beginning of this chapter, after a variable is declared,
true or false. These variables are particularly useful when we want to execute we must provide its initial value. We cannot use a variable if it was not
an instruction or a set of instructions only if a condition is met. To declare a initialized. This means that a variable has a valid value all the time. But this
is not always possible. Sometimes we do not have an initial value to assign
Boolean variable, we can specify the data type as Bool or let Swift infer it
to the variable during development or we need to indicate the absence of
from the value, as in the following example.
a value because the current one becomes invalid. For these situations,
Swift defines a modifier that turns every data type into an optional type.
Listing 2-25: Declaring a Boolean variable
This 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
var valid = true
name.

Listing 2-26: Declaring an optional variable of type Int


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, var mynumber: Int?
we just need to check whether the value is equal to true or false to verify the 

condition. We will see some practical examples later.


New values are assigned to optionals as we do with normal variables.

Listing 2-27: Assigning new values to optional variables



var mynumber: Int?
mynumber = 5

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
need to empty the variable, we can assign the keyword nil to it.

Listing 2-28: Using nil to empty an optional variable

 read the value of total later, we must unwrap it as we did with mynumber
var mynumber: Int? before.
mynumber = 5
mynumber = nil
 IMPORTANT: Before unwrapping an optional, we need to make
sure it contains a value (it is not equal to nil). If we try to unwrap an
This example declares an optional integer, assigns the value 5 to it, and empty optional, the app will return an error and crash. Later in this
then declares the variable as empty with the keyword nil. Although chapter we will learn how to use conditional statements to check
optionals seem to work like regular variables, they do not expose their this condition.
values. To read the value of an optional, we must unwrap it by adding an
There are times when we know that an optional will always have a value,
exclamation mark at the end of the name.
but we do not know what the initial value is. For example, we could have a
Listing 2-29: Unwrapping an optional variable variable that receives a value from the system as soon as the application is
 executed. When the variable is declared in our code, we do not have a
var mynumber: Int? value to assign to it, but we know that the variable will have a value as
mynumber = 5
var total = mynumber! * 10 // 50 soon as the user launches the app. For these situations, Swift includes
 implicitly unwrapped optionals. These are optional variables declared with
the exclamation mark instead of the question mark. The system treats
The last statement of Listing 2-29 unwraps mynumber to get its value, these variables as optionals until we use them in a statement, as in the
multiplies this value by 10, and assigns the result to the total variable. This is following example.
only necessary when we need to use the value. If we just want to assign an
Listing 2-31: Declaring implicitly unwrapped optionals
optional to another optional, the process is as always.

Listing 2-30: Assigning an optional to another optional var mynumber: Int!
mynumber = 5
 var total = mynumber * 10 // 50
var mynumber: Int? 
mynumber = 5
var total = mynumber
 In this code, the mynumber variable was declared as an implicitly unwrapped
optional and it was later initialized with the value 5. Notice that it was not
In this example, the system infers the type of the total variable to be an necessary to write the exclamation mark when reading its value anymore.
optional of type Int and assigns the value of mynumber to it. If we want to The system unwraps the mynumber variable automatically to use its value in
the multiplication (this is only available for implicitly unwrapped optionals).
Tuples

A tuple is a type of variable that contains a group of one or more values of


equal or different type. It is useful when we need to store values that are
somehow related to each other. Tuples are declared with their values and
types separated by a comma and enclosed between parentheses.

Listing 2-32: Declaring a tuple with two values



var myname: (String, String) = ("John", "Doe")

In this example, the myname variable is declared to be a tuple that contains


two String values. The values of this tuple are of the same type, but we can
use any combination of values we want.

Listing 2-33: Declaring a tuple with values of different type



var myname = ("John", "Doe", 44)

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
at index 1, and so on. Using the corresponding index and dot notation we
can access the value we want.

Listing 2-34: Reading a tuple



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 and the
tuple is assigned to this construction. The values are assigned to the
Listing 2-35: Changing the value of a tuple variables in the same order they are in the tuple. If only some of the values

are required, the rest may be ignored with an underscore.
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 new value must be of the same data type as the old one
or we will get an error. After the code is executed, the value of mytext is
Only the variables name and age are created in this last example (notice the
"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.

Listing 2-36: Declaring names for the values of a tuple



var myname = (name: "John", surname: "Doe", age: 44)
var mytext = "\(myname.name) is \(myname.age) years old"

Swift also provides a way to copy the values of the tuple into independent
variables.

Listing 2-37: Creating multiple variables from the values of a tuple



var myname = ("John", "Doe", 44)
var (name, surname, age) = myname
var mytext = "\(name) \(surname) is \(age) years old"
2.4 Conditionals and Loops If and Else
 

Up to this point, we have been writing instructions in a sequence, one after A simple but handful conditional statement available in Swift is if. With if
another. In this programming pattern, the system executes each statement we can check a condition and execute a group of instructions only when
once. It starts with the one at the top and goes on until it reaches the end the condition is true. The instructions that are going to be executed must
of the list. The purpose of Conditionals and Loops is to break this
be declared between braces after the condition.
sequential flow. Conditionals allow us to execute one or more instructions
only when a condition is met, and Loops let us 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
instructions 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.

IMPORTANT: The instruction between braces in the example of


Listing 2-39 is displaced to the right. The whitespace on the left is
used to help us differentiate the statements between braces from

the rest of the statements. This whitespace is automatically When only two results are required, we may define the condition using a
generated for you by Xcode, but you can add it yourself, when Boolean. These values do not need to be compared with the expected
necessary, by pressing the Tab key on your keyboard. value; they already return a state (true or false).

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 

comparison operators available in Swift. var underage = true


var message = "John is allowed"
if underage {
== checks whether the value on the left is equal to the value on message = "John is underage"
}
the right. 
!= checks whether the value on the left is different from the value
on the right.
This code checks whether the value of the underage variable is true or false. If
> checks whether the value on the left is greater than the value
it is true (which means the condition is true), a new string is assigned to the
on the right.
message variable. If what we want is to execute the statements when the
< checks whether the value on the left is less than the value on
the right. value is false, Swift offers a logical operator to toggle the condition. All we
>= checks whether the value on the left is greater or equal than need to do is to precede the condition with an exclamation mark.
the value on the right.
<= checks whether the value on the left is less or equal than the Listing 2-42: Using logical operators
value on the right. 
var underage = true
var message = "John is underage"
All these operators are applied the same way we did in the previous if !underage {
example. For instance, the following code modifies the value of message message = "John is allowed"
}
when the value of age is less or equal than 21. 

Listing 2-40: Comparing two values with the <= operator


The original value of the underage variable in the code of Listing 2-42 is true,

so when the if statement toggles the condition, the resulting condition is
var age = 21
var message = "John is old" false and therefore the value of the message variable is not modified.
if age <= 21 { The exclamation mark is part of a group of logical operators provided by
message = "John is young"
} Swift.

! (logical NOT) toggles the state of the condition. If the condition you can declare them within parentheses, as in (true && false) ||
is true, it returns false, and vice versa. true. The expression within the parentheses is evaluated first, and
&& (logical AND) checks two conditions and returns true if both the result is then evaluated against the rest of the expression.
are true.
|| (logical OR) checks two conditions and returns true if one or Although we can use comparison operators and logical operators in most
both are true. of the data types available, optionals are slightly different. Their values are
wrapped, so we cannot compare them with other values or check their
Logical operators work with any kind of conditions, not only Booleans. To state as we do with Booleans. Optionals must be compared against the
work with complex conditions, it is recommended to enclose the condition
keyword nil first and then unwrapped before working with their values.
between parentheses.
Listing 2-44: Checking whether an optional contains a value or not
Listing 2-43: Using logical operators to check several conditions


var count = 0
var smart = true var myoptional: Int? = 5
var age = 19
var message = "John is underage or dumb" if myoptional != nil {
let uvalue = myoptional!
if (age < 21) && smart { count = count + uvalue // 5
message = "John is allowed" }
} 

This example introduces the process we must follow to read the value of
The if statement in Listing 2-43 compares the value of the age variable with an optional variable. The optional is checked first against nil. If it is different
21 and checks the value of the smart variable. If age is less than 21 and smart from nil (which means it contains a value), we unwrap the optional inside
is true, then the overall condition is true, and a new string is assigned to the block of statements using an exclamation mark, assign its value to a
message. If any of the individual conditions is false, then the overall condition constant, and use the constant to perform any operation necessary.
is false, and the block of instructions is not executed. In this case, both We always need to make sure that an optional has a value before
conditions are true and therefore the "John is allowed" string is assigned to unwrapping it. Because of this, Swift introduces a convenient syntax that
message. checks the optional and unwraps its value at the same time. It is called
Optional Binding.
IMPORTANT: Using && (AND) and || (OR) you can create a logical
sequence of multiple conditions. The system evaluates one condition Listing 2-45: Using optional binding to unwrap an optional variable
at a time from left to right and compares the results. If you want to 
make sure that the expressions are evaluated in the correct order, var count = 0

var myoptional: Int? = 5 executed only if both conditions are true (the myoptional variable contains a
if let uvalue = myoptional {
count = count + uvalue // 5 value and the value is equal to 5).
} Sometimes, a group of instructions must be executed for each state of the

condition. For this purpose, Swift includes the if else statement. The
This code is cleaner and easy to read. The optional is unwrapped as part of instructions are declared in two blocks. The first block is executed when
the condition. If it is different from nil, its value is assigned to the uvalue the condition is true, and the second block when the condition is false.
constant and the statements in the block are executed, otherwise, the
statements inside the block are ignored. Listing 2-47: Using if else to respond to both states of the condition

var mynumber = 6
IMPORTANT: The constant created to unwrap the optional in this if mynumber % 2 == 0 {
way is only available inside the block assigned to the if statement. If mynumber = mynumber + 2 // 8
we try to read the value of this constant from outside the block, we } else {
mynumber = mynumber + 1
will get an error. We will learn more about the scope of variables in }
Chapter 3. 

If we want to unwrap several optionals at the same time using Optional This is a simple example that checks whether a value is odd or even using
Binding, we must declare the expressions separated by a comma. This also the remainder operator. The condition gets the remainder of the division
applies when we want to check other conditions in the same statement. between the value of the mynumber variable and 2 and compares the result
For instance, the following example unwraps an optional and only executes against 0. If true, it means that the value of mynumber is even, so the first
the code between braces if its value is equal to 5. block is executed. If the result is different from 0, it means that the value is
odd and the condition is false, so the block corresponding to the else
Listing 2-46: Checking multiple conditions with Optional Binding
instruction is executed instead.

The statements if and else may be concatenated to check for as many
var count = 0
var myoptional: Int? = 5 conditions as we need. In the following example, the first condition checks
if let uvalue = myoptional, uvalue == 5 { whether age is less than 21. If not true, the second condition checks
count = count + uvalue // 5
} whether age is over 21. And if not true, the final else block is executed.

Listing 2-48: Concatenating if else instructions


The if statement in Listing 2-46 unwraps the optional first and, if there is a 
value, compares it with the number 5. The statements in the block are var age = 19
var message = "The customer is "
if age < 21 {
message += "underage" // "The customer is underage" This code defines an optional variable called age with the value 19. Next, we
} else if age > 21 {
message += "allowed" use a ternary operator to unwrap it and assign its value to a new variable
} else { called realage. If the optional contains a value, its value is assigned to the
message += "21 years old"
} variable, otherwise, the value 0 is assigned instead.

Assigning values by default when the optional is empty is very common. To
simplify our work, Swift offers the nil-coalescing operator, which is
If all we need from an if statement is to assign a value to a variable
represented by the characters ??. This operator works like the ternary
depending on a condition, we can use a shortcut provided by Swift called
operator implemented before; it unwraps the optional and returns its
ternary operator. The ternary operator is a construction composed by the
value or returns another value if the optional is empty. In the following
condition and the two values we want to return for each state, separated
example, we create an empty optional called age and use the nil-coalescing
with the characters ? and :, as in the following example.
operator to assign its value to the maxage variable or the value 100 if the
optional is empty.
Listing 2-49: Implementing the ternary operator

var age = 19
Listing 2-51: Unwrapping an optional with the nil-coalescing operator
var message = age < 21 ? "Underage" : "Allowed" // "Underage" 
 var age: Int?
var maxage = age ?? 100 // 100
The first value is returned if the condition is true, and the second value is 

returned if the condition is false. The advantage of using the ternary


operator is that it reduces the code significantly, but the result is the same
as using an if else statement. In this case, the string “Underage” is assigned
to the variable message because the value of age is less than 21.
Ternary operators can also be implemented to unwrap optionals. For
instance, we can check whether an optional variable contains a value and
assign it to another variable or give the variable a default value if the
optional is empty.

Listing 2-50: Unwrapping an optional with a ternary operator



var age: Int? = 19
var realage = age != nil ? age! : 0 // 19

Switch When we need to execute the same set of instructions for more than one
value, we can declare the values separated by comma.

Listing 2-53: Checking multiple conditions per case
We can repeat the if and else statements to check as many conditions as we 
need, but this pattern can make the code impossible to read and maintain. var age = 6
When several conditions must be verified, it is better to use the switch var message = "You go to "
switch age {
instruction instead. This instruction compares a value with a list of values case 2, 3, 4:
and executes the statements corresponding to the value that matches. The message += "Day Care"
case 5, 6, 7, 8, 9, 10, 11:
possible matches are listed between braces using the case keyword, as in message += "Elementary School" // "You go to Elementary School"
the following example. case 12, 13, 14, 15, 16, 17:
message += "High School"
case 18, 19, 20, 21:
Listing 2-52: Checking conditions with switch message += "College"
default:
 message += "Work"
var age = 19 }
var message = "" 
switch age {
case 13:
message = "Happy Bar Mitzvah!"
The switch statement can also work with more complex data types,
case 16: such as strings and tuples. In the case of tuples, switch provides additional
message = "Sweet Sixteen!"
case 21:
options to build complex matching patterns. For example, the following
message = "Welcome to Adulthood!" code checks the second value of a tuple to determine the difference in age.
default:
message = "Happy Birthday!" // "Happy Birthday!"
} Listing 2-54: Matching a tuple in a switch statement
 
var message = ""
The cases must be exhaustive; every possible value of the variable being var ages = (10, 30)
checked must be contemplated. If we do not include a case statement for switch ages {
every possible value, we must add a default statement at the end that is case (10, 20):
message = "Too close"
executed when no match is found. case (10, 30):
In Listing 2-52, we compare the value of the age variable with a small set of message = "The right age" // "The right age"
case (10, 40):
values corresponding to special dates. If no case matches the value of the message = "Too far"
variable, the default statement is executed and the string "Happy Birthday!" default:
message = "Way too far"
is assigned to message. }
 message = "Way too far"
}

This example always compares the first value of the tuple against 10
but checks different matches for the second value. If a value does not
matter, we can use an underscore to ignore it. 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
Listing 2-55: Matching only the second value of a tuple can access and use the value from the statements inside the case (in this
 example, we just add the value to a string).
var message = "" There is an even more complex matching pattern that involves the use
var ages = (10, 30) of a clause called where. This clause allows us to check additional conditions.
switch ages { In the following example, we capture the values of the tuple with another
case (_, 20): tuple and compare them against each other.
message = "Too close"
case (_, 30):
message = "The right age" // "The right age" Listing 2-57: Comparing values with where
case (_, 40): 
message = "Too far"
default: var message = ""
message = "Way too far" var ages = (10, 20)
}
 switch ages {
case let (x, y) where x > y:
message = "Too young"
An alternative offered by the switch statement to create complex case let (x, y) where x == y:
matching patterns is to capture a value in a constant to be able to access it message = "The same age"
case let (x, y) where x < y:
from the instructions of the case. message = "Too old" // "Too old"
default:
message = "Not found"
Listing 2-56: Capturing values with constants }
 
var message = ""
var ages = (10, 20) Every time the switch statement tries to match a case in this example, it
switch ages { creates a tuple and assigns the values of ages to it. The where clause
case (let x, 20): compares the values and when the condition returns true it executes the
message = "Too close to \(x)" // "Too close to 10"
case (_, 30):
statements inside the case.
message = "The right age"
case (let x, 40):
message = "Too far to \(x)"
default:

While and Repeat While


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 conditionals studied so far execute the statements only once.
the final value of counter will be 11 (its value is incremented once and then
Sometimes the program requires executing a block of instructions several
the condition returns false, ending the loop).
times until a condition is satisfied. An alternative offered by Swift to create
these loops is the while statement (and its sibling repeat while).
The while statement checks a condition and executes the statements in its
block while the condition is true. The following example initializes a
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
executed. After this, the condition is checked again. The loop keeps running
until the condition becomes false (the value of the counter variable is equal
or greater than 5).

Listing 2-58: Using while to create a loop



var counter = 0
while counter < 5 {
counter += 1
}

If the first time the condition is checked returns false, the statements in the
block are never executed. If we want to execute the statements at least
once, we must use repeat while.

Listing 2-59: Using repeat while to create a loop



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

For In Finally, the second statement adds the current value of letter to the end of
 the message string.
In this example, the code works as follows: in the loop's first cycle, the
character "H" is assigned to the letter constant. Because at this moment the
The purpose of the for in loop is to iterate over collections of elements, like
message string is empty, nothing is added by the first statement. Then, the
the strings of characters studied before. During the execution of a for in
loop, the system reads the elements of the collection one by one in second statement adds the value of letter to the current value of message and
sequential order and assigns their values to a constant that can be used by the next cycle is executed. In this new cycle, the character "e" is assigned
the statements inside the block. In this case, the condition that must be to the letter constant. This time, the message string already contains the letter
satisfied for the loop to be over is reaching the end of the collection. "H", so the character "-" is added at the end by the first statement ("H-"),
The syntax of a for in loop is for constant in collection {}, where constant is the 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
name of the constant that we are going to use to capture the value of each
variable are processed. The final value of message is "H-e-l-l-o".
element, and collection is the name of the collection of values that we want
When the constant is not required inside the block, we can replace it with
to iterate over.
an underscore.
Listing 2-60: Using for in to iterate over the characters of a string
Listing 2-61: Iterating over a string without reading the characters

var mytext = "Hello" 
var message = "" var mytext = "Hello"
var counter = 0
for letter in mytext {
message += message != "" ? "-" : "" for _ in mytext {
message += "\(letter)" counter += 1
} }
 var message = "The string contains \(counter) letters" // 5

The code in Listing 2-60 defines two String variables: mytext with the text
"Hello" and message with an empty string. Next, we use a for in loop to iterate In this example, we iterate over the value of mytext to count the number of
over the characters of the string in mytext and add each character to the characters in the string. The value of the counter variable is incremented by
current value of message. In each cycle of the loop, the instruction takes one 1 each cycle, giving a total of 5.
character from the value of mytext, assigns it to the letter constant, and A for in instruction may include the where clause to perform the next cycle
executes the statements in the block. The first statement uses a ternary only when a condition is met. For instance, the following code checks the
operator to check whether the value of message is an empty string. If not, it value of the letter and only performs the cycle when the letter is not an L.
adds the - character at the end of it, otherwise, it adds an empty string. In consequence, only the letters H, e, and o are counted.

Control Transfer Statements


Listing 2-62: Adding a condition to a loop


var mytext = "Hello"
var counter = 0 Sometimes loops must be interrupted, independently of the condition.
Swift offers instructions to break the execution of loops and conditionals.
for letter in mytext where letter != "l" {
counter += 1
The following are the most frequently used.
}
var message = "The string contains \(counter) letters" // 3

continue—This instruction interrupts the current cycle and moves to
the next. The system ignores the rest of the statements in the block
after the instruction is executed.
break—This instruction interrupts the loop. The rest of the
statements in the block and any pending cycles are ignored after the
instruction is executed.

The continue instruction is applied when we do not want to execute the rest
of the statements in the block, but we want to keep the loop running. For
instance, the following code counts the letters in a string but ignores the
letters "l".

Listing 2-63: Jumping to the next cycle of the loop



var mytext = "Hello"
var counter = 0

for letter in mytext {


if letter == "l" {
continue
}
counter += 1
}
var message = "The string contains \(counter) letters" // 3

The if statement inside the for in loop of Listing 2-63 compares the value of
letter with the letter "l". If the characters match, the continue instruction is
executed, the last statement inside the loop is ignored, and the loop moves Listing 2-65: Ignoring values in a switch statement
on to the next character in mytext. In consequence, the code counts all the 
characters that are different from "l" (H, e, and o). var age = 19
Unlike the continue instruction, the break instruction interrupts the loop var message = ""

completely, moving the execution of the program to the statements after switch age {
the loop. The following example only counts the characters in the string case 13:
message = "Happy Bar Mitzvah!"
that are placed before the first letter "l". case 16:
message = "Sweet Sixteen!"
case 21:
Listing 2-64: Interrupting the loop
message = "Welcome to Adulthood!"
 default:
var mytext = "Hello" break
var counter = 0 }

for letter in mytext {
if letter == "l" { 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
}
var message = "The string contains \(counter) letters" // 2 the statements after the switch.

Again, the if statement of Listing 2-64 compares the value of letter with the
character "l", but this time it executes the break instruction when a match is
found. If the character currently processed by the loop is "l", the break
instruction is executed, and the loop is over, no matter how many
characters are left in the string. In consequence, only the characters
located before the first letter "l" are considered (H and e).
The break instruction is also useful to cancel the execution of a switch
statement. The problem with the switch statement in Swift is that the cases
must be exhaustive, which means that every possible value must be
contemplated. When this is not possible or necessary, we can use the break
instruction to ignore the values that are not applicable. For example, we
can declare the cases for the values we need and then break the execution
in the default case for the rest of the values that we do not care about.

Guard the return instruction to interrupt the execution of a function. We will


study the return instruction and functions later.

The guard instruction is intended to prevent the execution of the code that
follows the statement. For example, we can break the execution of a loop
when a condition is satisfied, as we do with an if else statement.

Listing 2-66: Interrupting a loop with guard



var mytext = "Hello"
var counter = 0

for letter in mytext {


guard letter != "l" else {
break
}
counter += 1
}
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-66, the for in loop reads the
characters of the string in mytext one by one, as we did before. If the
characters are different from the letter "l", we increase 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.

IMPORTANT: The advantage of guard over the if else statement is


that the variable or constant defined in the condition outlives the
statement, and therefore we can read its value outside the block.
Although you can implement the guard instruction to break or
continue a loop, the instruction was introduced to work along with
CHAPTER 3 - SWIFT PARADIGM 3.1 Programming Paradigms

Programs wouldn’t be very useful if we were only able to write them as a


consecutive set of instructions. At first, this was the only way to write a
program, but soon programming languages incorporated tools to allow
programmers to group instructions together and execute them every time
necessary. The way instructions are organized is called paradigm. Different
paradigms are now available, with the most common being the Object-
Oriented Programming paradigm, or OOP. This paradigm emerges from the
construction and integration of processing units called objects. Swift
adopts OOP, but it is not focused as much on objects as other languages
do. Instead, it implements other types of processing units called structures
and enumerations along with blueprints called protocols to conform a new
paradigm called Protocol-Oriented Programming. The Swift paradigm
unifies objects, structures, and enumerations through protocols that define
how these units behave and the type of functionality they have.

Do It Yourself: The examples in this chapter were designed to be


tested in Playground. You just need to create a Playground file with a
Blank template and then replace the code with the example you
want to try.

3.2 Functions Declaration of Functions


 

The processing units that define the Swift paradigm (objects, structures, Functions are declared with the func keyword followed by a name,
and enumerations) are capable of encapsulating data along with parentheses, and the code between braces.
functionality. The data is stored in the same variables we studied before,
but the functionality is provided by functions. Functions are blocks of code Listing 3-1: Declaring and calling functions
delimited by curly braces and identified by a name. The difference between 
functions and the block of codes used in loops and conditional statements var mynumber = 5
is that there is no condition to satisfy; the statements inside a function are func myfunction() {
mynumber = mynumber * 2 // 10
executed every time the function is called (executed). Functions are called }
by writing their names followed by parentheses. This call may be myfunction()
performed from anywhere in the code and every time necessary, which 
completely breaks the sequential processing of a program. Once a function
is called, the execution of the program continues with the statements The code in Listing 3-1 declares the mynumber variable, initializes it with the
inside the function and only returns to the section of the code that called value 5, and then declares a function called myfunction(). The statements in a
the function once the execution of the function is over. function are only processed when the function is called, so after the
myfunction() function is declared, we call it with the instruction myfunction().
When our function is called, it multiplies the current value of mynumber
times 2 and assigns the result back to the variable. Once all the statements
inside the function are processed, the execution continues from the
statement after the call.
As we already mentioned, once the function is declared, we can call it any
time necessary and from anywhere in the program. For example, the
following code runs a while loop that calls myfunction() a total of 5 times (the
loop runs while counter is less than 5). Every time the function is executed,
mynumber’s current value is multiplied by 2, getting a result of 160.

Listing 3-2: Calling functions from a loop



var mynumber = 5
var counter = 0

func myfunction() {
mynumber = mynumber * 2 // 160 In this example, the function is declared with one parameter of type Int
}
while counter < 5 { called number.
myfunction() The call must include the name of the parameter and the value we
counter += 1
} want to send to the function. When the function of Listing 3-3 is called, the
 value between the parentheses of the call (5) is assigned to the number
constant, the value of the constant is multiplied by 2, and finally the result
The functions in these examples are modifying the value of an external is included in a string with string interpolation.
variable (a variable that was not declared inside the function). Creating a Of course, we can include as many parameters as we need. The
function that works with values and variables that do not belong to the following example multiplies two values and creates a string with the
function itself could be dangerous; some variables may be modified by result.
accident from other functions, the function may be called before the
variables were even declared or initialized, or the variables that the Listing 3-4: Sending different values to a function
function tries to modify may not be accessible by the function (functions 
have limited scope, as we will see later). To make sure that a function func multiply(number1: Int, number2: Int) {
processes the right values, they must be sent to the function when it is let result = number1 * number2
let message = "The result is \(result)" // "The result is 80"
called. The type of values the function can receive and the names they are }
going to take are specified within the function’s parentheses separated by multiply(number1: 20, number2: 4)

a comma. When the function is executed, these parameters are turned into
constants that we can read inside the function to get their values.
Functions may not only be called every time we need them, but also the
values we provide to the function in the call may be different each time.
Listing 3-3: Sending values to a function
This makes functions reusable.

func doubleValue(number: Int) {
let total = number * 2 Listing 3-5: Sending different values to a function
let message = "Result: \(total)" // "Result: 10" 
}
doubleValue(number: 5) func doubleValue(number: Int) {
let total = number * 2

let message = "Result: \(total)"
}
In this example, we don't use external variables anymore. The value to doubleValue(number: 5) // "Result: 10"
doubleValue(number: 25) // "Result: 50"
be processed is sent to the function when it is called and received by the 
function through its parameter. The parameters are declared within the
function’s parentheses with the same syntax used for constants and The constants and variables declared inside a function, like total and message,
variables. We must write the name and the data type separated by a colon. are not accessible from other parts of the code. This means that a function

can receive values, but the result produced by processing those values is Listing 3-7: Returning a tuple
trapped inside the function. To communicate this result to the rest of the 

code, functions can return a value using a special instruction called return. func sumCharacters(word: String) -> (String, Int) {
var characters = ""
The return instruction finishes the processing of the function, so we must var counter = 0
declare it after all the statements required have been processed, as in the for letter in word {
characters += "\(letter) "
following example. counter += 1
}
return (characters, counter)
Listing 3-6: Returning a value from a function }
 var (list, total) = sumCharacters(word: "Hello")
var message = "There are \(total) characters (\(list))"
func doubleValue(number: Int) -> Int { 
let total = number * 2
return total
} The sumCharacters() function of Listing 3-7 receives a string (word: String) and
let result = doubleValue(number: 25)
returns a tuple composed of a string and an integer (-> (String, Int)). The
let message = "The result is \(result)" // "The result is 50"
 function adds the characters to the characters variable and counts them with
the counter variable, as we did before (see Listing 2-60). At the end, the
When we create a function that returns a value, the type of the value tuple is returned, its values are assigned to the list and total variables, and
returned is specified in the declaration after the parentheses with the then incorporated into a string ("There are 5 characters (H e l l o )").
syntax -> Type, where Type is just the data type of the value that is going to Besides returning the result of an operation, the return instruction can also
be returned by the function. A function can only return values of the type be used to interrupt the execution of a function. The guard instruction
indicated in its definition. For instance, the function in Listing 3-6 can only introduced in Chapter 2 is perfect for cases like this, as illustrated by the
following example.
return integer values because we declared the returned type as -> Int.
When a function returns a value, the system calls the function first and
Listing 3-8: Interrupting the execution of a function with guard
then the value returned is processed inside the statement that made the

call. For instance, in the code of Listing 3-6, we create the result variable and func doubleValue(number: Int) -> Int {
assign to this variable a call to the doubleValue() function. When the system guard number < 10 else {
return number
processes this statement, the function is executed first and then the value }
returned (50) is assigned to the variable. return number * 2
}
The values received and returned by a function may be of any available let result = doubleValue(number: 25)
data type. The following example takes a string and returns a tuple with a let message = "The result is \(result)" // "The result is 25"
string and an integer. 
The doubleValue() function of Listing 3-8 is similar to previous examples. It
receives a number, multiplies it by 2, and returns the result, but this time Listing 3-10: Modifying external variables from a function
we first check that the value received by the function is less than 10. If the 
value is equal or higher than 10, the guard instruction calls the return func first() {
instruction with the received value, otherwise, the statements of the var number = 25
second(value: &number)
function are executed as normal. In this case, the value sent to the function print("The result is \(number)") // "The result is 50"
is 25, therefore the condition is false, and the same value is returned. }
func second(value: inout Int) {
Notice that in the example of Listing 3-8 we simplified our code including value = value * 2
the multiplication in the return instruction. The return instruction can take }
first()
single values or expressions like this. The instruction takes care of solving 
the expression (or operation, as in this case) and returning the result. For
this reason, sometimes we may find functions with only one statement in This code defines two functions: first() and second(). The second() function
charge of returning a value. If this is the case, we can remove the return receives an inout parameter called value, which means that any modification
keyword. In the following example, the call sends the number 25 to the on its value is stored in the original variable. The first() function defines a
function, the value is multiplied by 2, and returned, as in previous variable called number and then executes the second() function with it, so
examples, but this time we didn't have to declare the return keyword when the second() function multiplies this value times 2, the result (50) is
because there is only one statement inside the function and therefore the stored in number. At the end, we execute the first() function to start the
compiler knows what to return. process. Notice that in the call to the second() function we include an
ampersand before the variable's name (&). This tells the system that the
Listing 3-9: Removing the return keyword variable is going to be modified by the function.
 An important aspect of the definition of a function are the parameter's
func doubleValue(number: Int) -> Int { names. When we call a function, we must declare the names of the
number * 2
} parameters. For example, the function doubleValue() of previous examples
let result = doubleValue(number: 25) includes a parameter called number. Every time we call this function, we
let message = "The result is \(result)" // "The result is 50"

must include the name of the parameter (e.g., doubleValue(number: 50)). These
names are called argument labels. Swift automatically generates argument
Besides the return keyword, Swift offers the inout keyword to preserve a labels for every parameter using their names. Sometimes the names
value after the function finishes processing. When a parameter is marked assigned to the parameters of a function may be descriptive enough for
with inout, any changes performed on the value are stored in the original the statements of the function but may be confusing when we perform the
variable. This is useful when we call a function from another function (or call. For cases like these, Swift allows us to define our own argument labels
block), and we want the modifications introduced by the second function in the function's definition; we just need to declare them before the name
to persist. of the parameter separated by a space.

Listing 3-13: Declaring default values for parameters


Listing 3-11: Declaring argument labels 
 func sayhello(name: String = "Undefined") -> String {
return "Your name is " + name
func doubleValue(years number: Int) -> Int {
}
number * 2
} let message = sayhello() // "Your name is Undefined"

let result = doubleValue(years: 8)
let message = "The result is \(result)" // "The results is 16"
 The code in Listing 3-13 declares the function sayhello() with one
parameter of type String called name and with the string "Undefined" as its
The doubleValue() function in Listing 3-11 declares an argument label called
default value. When the function is called without a value, the string
years for the number parameter. From now on, the name of the parameter
"Undefined" is assigned to name.
(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 used when
calling the function to identify the value.
If what we want instead is to remove an argument label, we can define it
with an underscore.

Listing 3-12: Removing argument labels



func multiply(number1: Int, _ number2: Int) -> Int {
number1 * number2
}
let result = multiply(number1: 25, 3)
let message = "The result is \(result)" // "The result is 75"

In this example, we preserved the behavior by default for the first


parameter and removed the argument label for the second parameter.
Now the call only has to include the argument label of the first parameter
(multiply(number1: 25, 3)).
Every function we have defined so far requires the values to be
specified in the call. We cannot omit any of the values that the function
expects to receive, but Swift allows us to declare a default value for any of
the function’s parameters and avoid this requirement.
Generic Functions to maintain, which can introduce errors. In cases like this, we can declare
only one function with a generic data type.

Generic data types are placeholders for real data types. When the function
is called, the generic data type is turned into the data type of the value
Although creating two or more functions with the same name is not
received. If we send an integer, the generic data type turns into an Int data
allowed, we can do it if their parameters are not the same. This is called
type; if we send a string, it turns into a String. To define a generic function,
overloading and allows us to define multiple functions with the same name
we must declare the generic data type using a custom name between
to process different types of values.
angle brackets after the function's name, as in the following example.
Listing 3-14: Declaring different functions with the same name
Listing 3-15: Defining generic functions


func getDescription(value: Int) -> String {
let message = "The value is \(value)" func getDescription<T>(value: T) -> String {
return message let message = "The value is \(value)"
} return message
func getDescription(value: String) -> String { }
let message = "The value is \(value)" let result1 = getDescription(value: 3.5) // "The value is 3.5"
return message let result2 = getDescription(value: "George") // "The value is George"
}

let result1 = getDescription(value: 3) // "The value is 3"
let result2 = getDescription(value: "John") // "The value is John" 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
These functions have the same name, but one receives an integer and the
than the two functions from the previous example, but now we have
other a string. We can say that the function that receives the string
reduced the amount of code in our program. When the function is called,
overloads the function that receives the integer. When we call the
the T generic data type is converted into the data type received and the
getDescription() function, the system selects which function is going to be
value is processed (The first time the function is called in our example, T is
executed depending on the value of the argument (when we call the
turned into a Double and the second time into a String).
function with an integer, the first function is executed, and when we call it
In our example, we only use one parameter and therefore the function
with a string, the second function is executed).
can only work with one data type, but we can declare two or more generic
The advantage of creating functions with the same name is that there is
data types separated by commas (e.g., <T, U>).
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
IMPORTANT: Although we can send any value of any type we want
to a generic function, the operations we can perform on them are
in the type of value received, we end up with two or more pieces of code

very limited due to the impossibility of the compiler to know the Standard Functions
nature of the values received. For example, we can add two integers,

but we can't add two Boolean values. To solve these issues, we can
constraint the generic data types with protocols. We will study how
to define protocols and how to use them later in this chapter.
The main advantage of functions is that we can call them from any part of
the program that has access to them, and they will always perform the Of all the functions in the Swift Standard Library, print() is probably the most
same operations. We don't even need to know how the function does it, useful. Its purpose is to print messages on the Xcode's console that may
we just send to the function the values we want to process and read the help us to fix bugs in our code. In the following example, we use it to print
result. Because of these features, functions can be shared, and the result of two operations.
programmers can implement in their code pre-programmed functions
provided by libraries and frameworks to incorporate additional Listing 3-16: Printing values on the console with print()
functionality that would take them too long to develop themselves. 
All the features of the Swift language we have implemented so far are let absolutenumber = abs(-25)
let minnumber = min(absolutenumber, 100)
included in a library called Standard Library. The Standard Library includes print("The number is: \(minnumber)") // "The number is: 25"
everything, from operators to primitive data types, as well as predefined 
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 minimum value between absolutenumber
and the number 100 with the min() function, and finally prints a message on
abs(Value)—This function returns the absolute value of an integer.
the console with the result.
max(Values)—This function compares two or more values and Sequences or collections of values are very important in computer
returns the largest.
programming. The strings studied in Chapter 2 are a clear example. A string
min(Values)—This function compares two or more values and is a sequence of values of type Character. As we will see later in this chapter,
returns the smallest. the Swift Standard Library includes several types of collections to store
sequences of values that are important to the application or the user, but it
There are also functions available to stop the execution of the application
also offers a few functions to create sequences of values our application
in case of an unrecoverable error.
may need temporarily to process information. The following are some of
the most frequently used.
fatalError(String)—This function stops the execution of the
application and prints on the console the message provided by the
stride(from: Value, through: Value, by: Value)—This function
argument.
returns a collection of values from the value specified by the from
precondition(Bool, String)—This function stops the execution of argument to the value specified by the through argument in intervals
the application and prints a message on the console if a condition is specified by the by argument.
false. The first argument is the condition to be checked and the
stride(from: Value, to: Value, by: Value)—This function returns a
second argument is the message we want to print on the console.
collection of values from the value specified by the from argument to

the value specified by the through argument in intervals specified by over the values of the finalsequence collection and print them on the console
the by argument. The last value is not included. ("Hello - 0", "Hello - 2", "Hello - 4", "Hello - 6", "Hello - 8").
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.

The following example applies some of these functions to create a list of


tuples that contain a string and an integer.

Listing 3-17: Creating collections of values



let sequencetext = repeatElement("Hello", count: 5)
let sequencenumbers = stride(from: 0, to: 10, by: 2)
let finalsequence = zip(sequencetext, sequencenumbers)

for (text, number) in finalsequence {


print("\(text) - \(number)")
}

The code in Listing 3-17 calls the repeatElement() function to create a


collection of 5 elements, all of them with the string "Hello" ("Hello",
"Hello", "Hello", "Hello", "Hello"). Next, the stride() function creates another
collection with integers from 0 to 10, increased by 2, and without including
the last one (0, 2, 4, 6, 8). Next, the zip() function merges these two
collections in one collection of tuples, where each tuple has the
corresponding values of each collection; the first tuple contains the first
value of the sequencetext collection along with the first value of the
sequencenumbers collection, and so on. Finally, we use a for in loop to iterate
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 between 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 different variables. When we read the value of multiplier in the second()
from the statements inside the block in which they were created (and the function to add a new value to the total variable, the multiplier that the
statements from blocks created inside their block). For better system reads is the one declared inside the function because in that space
understanding, here is a practical example. this constant has precedence over the global variable with the same name
(we can declare variables and constants with the same name as long as
Listing 3-18: Using variables and constants of different scopes
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 them 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 {
everything goes between 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

(1 to 9) and returns the result, but because we included the parentheses at
let multiplier = { (number: Int, times: Int) -> Int in
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. The task
}
print("The result is \(multiplier(10, 5))") // "The result is 50" performed in this example is simple but executing a closure as soon as it is

declared is a technique usually implemented for more complex processes
such as loading a file or opening a database.
This example defines a closure and assigns it to the multiplier constant. After
If the closure does not receive any parameter, we can simplify its syntax
this, the name of the constant may be used to execute the closure. Notice
declaring the type of the constant or variable and letting Swift infer the
that the parameters of the closure and the return type are declared with
type of the value returned by the closure. Notice that when the closure
the same syntax as functions ((number: Int, times: Int) -> Int), but the
doesn't receive a value, we can also remove the in keyword.
parameters' names are not turned into argument labels and therefore they
are ignored in the call.
Listing 3-21: Simplifying a closure
 sent to the function. The function assigns the closure to the myclosure
let myaddition: Int = { constant, and the closure is executed inside the function using this name
var total = 0
let list = stride(from: 1, through: 9, by: 1) and the values 10 and 2, producing the result 20.
The closure was defined in the global space and was executed inside the
for number in list {
total += number processclosure() function, but we don't need to assign the closure to a
} variable, we can just define it in the 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 variables but also sent and returned print("The total is: \(myclosure(10, 2))") // "The total is: 20"
from functions, as any other value. When a function receives a closure, the }
parameter's data type only has to include the data types the closure processclosure(myclosure: { (number: Int, times: Int) -> Int in
return number * times
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 argument. This can be simplified even further by using a pattern called
return total
} 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"
} Listing 3-24: Using Trailing Closures
processclosure(myclosure: multiplier)
 
func processclosure(myclosure: (Int, Int) -> Int) {
print("The total is: \(myclosure(10, 2))") // "The total is: 20"
The first statement in Listing 3-22 defines a closure that multiplies two
}
integers and returns the result. A function that receives a closure of this 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 as the last argument of the function and therefore the memory after the execution of the function is over. They are declared by
argument label is not necessary. preceding them with the @escaping keyword, as in the following example.
The code in Listing 3-24 works the same way as previous examples, the
only advantage is the reduction in the amount of code we had to write. Listing 3-26: Declaring escaping closures
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 composed by the $
symbol and an index starting from 0. The first value received by the closure In the code of Listing 3-26, we declare a variable called myclosure that stores
is represented by $0, the second value by $1, and so on. a closure that doesn't receive or return any values (() -> Void) and then we
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 assign that
 closure to the myclosure variable. Next, we call the passclosure() function with a
func processclosure(myclosure: (Int, Int) -> Int) { trailing closure that prints a message on the console. Up to this point, all
print("The total is: \(myclosure(10, 2))") // "The total is: 20"
} we are doing is passing the closure to the function and the function is
processclosure() { $0 * $1 } assigning that closure to the myclosure variable, so at the end we execute

the closure in myclosure and the message is printed on the console.
Again, the code is the same, but now the closure is extremely simple.
When it is executed from the processclosure() function, it receives the values
IMPORTANT: When we define the data type of a closure, we must
declare the data types of the values it receives and the data type of
10 and 2 and assigns them to the placeholders $0 and $1, respectively.
the values it returns. Therefore, if the closure doesn't return any
Then, it multiplies their values and returns the result.
value, we must declare the return type as Void, as we did in the
In the previous examples, we have executed the closure received by the
example of Listing 3-26.
function inside the same function, but there are situations in which a
closure must be executed outside the function. This usually applies to
asynchronous operations, as we will see later. If we want to execute a
closure received by the function from outside the function, we must
declare it as an escape closure. Escape closures are closures that remain in
3.3 Structures Definition of Structures
 

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 the data but data and functionality between braces.
also 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 Listing 3-27: Defining a structure
variables and constants (called properties) and functions (called methods). 
Later we can declare variables and constants of this type to store struct Item {
information with the characteristics defined by the structure. These values var name: String = "Not defined"
var price: Double = 0
(called instances) will 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 by itself does not create anything; it is just
delineating the elements of the type (also called members), like a blueprint
used to create the real structures. What we need to do to store values of
this new data type in memory, as we would do with any other data type, is
to declare a variable or constant of this type. The declaration is done as
before, but 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).

Listing 3-28: Declaring a variable of type Item



struct Item {
var name: String = "Not defined"
var price: Double = 0
}
var purchase: Item = Item()

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 modifying the properties of the instance, the system creates a new
variable. structure and assigns the values to the properties of that instance.
In the previous example, the properties of a new instance always take the For this to be possible, the structure must be stored in a variable so
values declared in the structure’s definition ("Not Defined" and 0), but we can it can be replaced by the new structure later.
change 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 price property of the
In this example, the properties of the Item structure and the purchase
Item structure was defined as a property of type Price. Instead of storing a
variable are declared as before, but this time we let Swift infer their data
single value, now it can store a structure with two properties, USD and CAD,
types. After the instance is created, new values are assigned to its
for American and Canadian dollars. When the Item structure is created and
properties using dot notation. Dot notation is not only used to assign new
assigned to the purchase variable, the Price structure for the price property is
values but also to read the current ones. At the end, we read and print the
also created with its values by default (var price: Price = Price()). By
values of the name and price properties on the console ("Product: Lamps $
concatenating the names of the variables and properties containing these
10.5").
structures we can read and modify any value we want. For instance, the
last statement in the code in Listing 3-30 accesses the USD property of the
IMPORTANT: Notice that we stored the structure in a variable (var). price structure inside the purchase structure to assign a price to the item in
This is to be able to assign new values to its properties later. When American Dollars (purchase.price.USD = 10.50).
the values of the properties in a structure are modified, instead of
In this example, the price object is created during instantiation, but this is but when all the optionals have values, the instruction performs the task
not usually the case. Sometimes the values of the properties containing (in this case, assigning the number 10.50 to the USD property).
objects 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 is a simple tool to access objects, properties and
methods in a hierarchical chain that contains optional components. As
always, the access to these components is done through 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 processing the expression only in case of success. Here
is the same example, but with the price property turned into an optional.

Listing 3-31: Accessing optional properties



struct Price {
var USD = 0.0
var CAD = 0.0
}
struct Item {
var name: String = "Not defined"
var price: Price?
}
var purchase = Item()
purchase.name = "Lamps"
purchase.price?.USD = 10.50 // nil

The price property in this code is declared as an optional (its initial


value is not defined). Every time we read this property, we must unwrap its
value, but if we use Optional Chaining, we can just concatenate the values
with dot notation and add a question mark after the name of the optional
(purchase.price?.USD). The system reads every component in the instruction
and checks their values. If any of the optionals have no value, it returns nil,

Key Paths are defined as constants, the key path is created of type KeyPath (a read-
only key path). In the last statement, we use this key path to access the

value of the price property of the purchase instance and print it on the
console.
Besides using dot notation to read and write a property, we can use key
We can easily create a read-and-write key path by defining the structure's
paths. A key path is a reference to a property that we can use to read and
properties as variables. In the following example, we turn the name and price
modify its value. The advantage of using key paths instead of dot notation
properties into variables and modify the value of price using our keyPrice key
is that they are stored in structures and therefore we can pass them to
path.
other parts of the code and then use them to access the values of the
properties they are referencing without even knowing what those
Listing 3-33: Using read and write key paths
properties are. This can be useful when we need to interact with

frameworks, or when we are extending code to include our own
struct Item {
functionality. var name: String
Swift defines several structures to store key paths. For instance, a read-only var price: Double
}
key path is stored in an instance of a structure of type KeyPath and read- var purchase: Item = Item(name: "Lamps", price: 27.50)
and-write key paths are stored in a structure of type WritableKeyPath. The
syntax to define a key path includes a backward slash and the name of the let keyPrice = \Item.price
purchase[keyPath: keyPrice] = 30.00
data type followed by the name of the property we want to reference. To print(purchase.price)
access the value of a property using a key path, Swift offers a syntax that 

includes square brackets after the instance's name and the keyword keypath,
as illustrated in the following example.

Listing 3-32: Creating key paths



struct Item {
let name: String
let price: Double
}
var purchase: Item = Item(name: "Lamps", price: 27.50)

let keyPrice = \Item.price


print(purchase[keyPath: keyPrice]) // "27.5"

The 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 statements 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 may 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 their
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 its

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 the Item structure defined before to store information about var purchase = Item(name: "Lamp", price: 10.50)
different products. Each product will have its own name and price, so the print("Purchase: \(purchase.name) $ \(purchase.price)")
properties of each instance must be initialized with the proper values. The 

initialization of an instance is a very common process, and it would be far


The two forms of initialization we have seen so far are not customizable
too cumbersome if we had to assign the values one by one after the
enough. Some structures may have multiple properties to initialize or even
instances are created. For this reason, Swift provides different alternatives
methods that must be executed right away to get the proper values for the
to initialize the values of a structure. One of them is called memberwise
instance to be ready. To add more alternatives, Swift provides a method
initializer.
called init(). The init() method is called as soon as the instance is created, so
Memberwise initializers detect the properties of the structure and declare
we can use it to initialize properties or perform operations and initialize
their names as argument labels. Using these argument labels, we can
them with the results.
provide the values for initialization between the parentheses of the
initializer. The following code implements a memberwise initializer to
Listing 3-38: Initializing properties from the init() method
initialize an instance of the Item structure declared in previous examples.

struct Price {
Listing 3-36: Initializing properties var USD: Double
 var CAD: Double
struct Item {
init() {
var name = "Not defined"
USD = 5
var price = 0.0
CAD = USD * 1.29
}
}
var purchase = Item(name: "Lamp", price: 10.50)
}
print("Purchase: \(purchase.name) $ \(purchase.price)")
var myprice = Price()

Memberwise initializers reduce the amount of code and simplify


When the instance is generated by the initializer, the properties are
initialization. Also, if we use the memberwise initializer, we can ignore the
created first and then the init() method is executed. Inside this method we
values by default declared in the definition.
can perform any operation we need to get the properties’ initial values. In
the example of Listing 3-38, we assign an initial value of 5 to the USD
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. Swift identifies each function by its name and parameters, so we can
declare several functions with the same name as long as they have
Listing 3-39: Declaring the parameters of the init() method different parameters. In the example of Listing 3-40, two init() methods
 were declared to initialize the instance of the structure. The first method
struct Price { receives a Double value with the name americans and the second method also
var USD: Double receives a Double value but with the name canadians. The right method will
var CAD: Double
be executed according to the argument included in the initializer. In this
init(americans: Double) { example, we use the argument canadians with the value 5, so the instance is
USD = americans initialized by the second 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.

Listing 3-40: Declaring multiple init() methods



struct Price {
var USD: Double
var CAD: Double

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. And as we have seen before, when a
types of properties called Computed Properties. These properties do not
block contains only one statement, it knows what to return, so we can also
store a value of their own, instead they have access to the rest of the
omit the return keyword. The previous example can therefore be simplified
properties of the structure and can perform operations to set and retrieve
as follows.
their values.
Two methods were included 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 between braces after the property’s name.
struct Price {
Although both methods are useful, only the get method is required. var USD: Double
var ratetoCAD: Double
Listing 3-41: Declaring computed properties var canadians: Double {
 USD * ratetoCAD
}
struct Price {
}
var USD: Double
var purchase = Price(USD: 11, ratetoCAD: 1.29)
var ratetoCAD: Double
print(purchase.canadians) // "14.190000000000001"

var canadians: Double {
get {
return USD * ratetoCAD Including the set method for the canadians property we can, for example,
}
} set a new price using the same currency.
}
var purchase = Price(USD: 11, ratetoCAD: 1.29)
print("Price in CAD: \(purchase.canadians)") // "Price in CAD: 14.19"
Listing 3-43: Adding the set method to set a new value
 
struct Price {
The structure defined in Listing 3-41 contains a stored property called var USD: Double
var ratetoCAD: Double
USD to store the price in American dollars, a stored property called var ratetoUSD: Double
ratetoCAD to store the exchange rate for Canadian dollars, and a computed
var canadians: Double {
property called canadians that converts the US dollars into Canadian dollars get {
and returns the result. Computed properties are like methods, they USD * ratetoCAD
}
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

var canadians: Double {


get {
USD * ratetoCAD
}
set(CAD) {
USD = CAD * ratetoUSD
}
}

Property Observers last value, we declare property observers for the price property. Every time
 a new value is assigned to the property, the willSet() and didSet() methods are
executed. Swift automatically creates a parameter called newValue for the
willSet() method to provide access to the value that is going to be assigned
The properties of an instance of a structure may be modified at any
moment by different processes, such as in response to user interaction or to the property, and a parameter called oldValue for the didSet() method to
events produced by the system. To inform an instance that one of its provide access to the property’s old value after the new value was assigned
properties was modified, Swift introduces Property Observers. (we can change the names of these parameters as we did for the set()
Property Observers are special methods, similar to get() and set(), that can method in Listing 3-44). In our example, when the willSet() method is
be added to a property to execute code before and after a value is assigned executed, the current value of price is subtracted from newValue to get the
to it. The methods are called willSet() and didSet(), and are declared between difference, and the result is assigned to the increment property. And in the
braces after the properties declaration. didSet() method, we assign the old price provided by oldValue to the oldprice
property to have access to the item's previous price later.
Listing 3-45: Adding observers to a property

struct Price {
var increment: Double = 0
var oldprice: Double = 0

var price: Double {


willSet {
increment = newValue - price
}
didSet {
oldprice = oldValue
}
}
}
var product = Price(price: 15.95)
product.price = 20.75
print("New price: \(product.price)") // "New price: 20.75"
print("Old price: \(product.oldprice)") // "Old price: 15.95"

The Price structure in Listing 3-45 includes three properties: increment,


oldprice, and price. We use the price property to store the value of an item,
the oldprice property to store the previous price, and the increment property
to store the difference between the old price and the new one. To set this
Type Properties and Methods property is a type property, only accessible from the type itself. The same
happens with methods.

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 access those struct Price {
var USD: Double
properties and methods. But there are times when being able to execute var CAD: Double
properties and methods from the definition itself makes sense. We might
need, for example, to get some information that affects all the instances, or static func reserve() -> Price {
return Price(USD: 10.0, CAD: 11.0)
call methods to 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 var reserveprice = Price.reserve()
print("Price in USD: \(reserveprice.USD) CAD: \(reserveprice.CAD)")
that type. 
Type properties and methods for structures are declared adding the
static keyword to their definition. Once a property or method is declared The structure in this example includes a type method called reserve().
with this keyword, they are only accessible from the definition itself. In the The method creates and returns an instance of the Price structure with
following example, we include a type property called currencies to inform standard values. This is a common procedure and another way to create
how many currencies the structure can handle. our own initializer. If we use the initializer by default, the values must be
provided every time the instance is created, but with a type method all we
Listing 3-46: Defining type properties need to do is to call the method on the type and we get in return an

instance configured with specific values. In our example, the values
struct Price {
correspond to a reserved price. We call the reserve() method on the Price
var USD: Double
var CAD: Double type, the method creates an instance of the Price structure with the values
10.0 and 11.0, and then this instance is assigned to the reserveprice variable.
static var currencies = 2
} At the end, the values of both properties are printed on the console to
print(Price.currencies) // 2 confirm that their values were defined by the reserve() 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 lets 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 received value. But generics data types are not struct MyStructure<T> {
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 generic
struct MyStructure<T> {
var myvalue:T data types. As with functions, generics only become useful when we
constrain the data using protocols. We will learn more about
func description() {
print("The value is: \(myvalue)") // "The value is: 5" generics in the following sections and study protocols at the end of
} this chapter.
}
let instance = MyStructure<Int>(myvalue: 5)
instance.description()

This example defines a generic structure called MyStructure with one


generic type called T. The structure contains a generic property called
myvalue and a method that prints a message with its value. After the
definition, we create an instance of this structure with an integer. The
system replaces the T with the Int type, creates the instance, and assigns
the value 5 to the myvalue property. In the last statement, we call the
description() method to print the value.
When we create an instance of a generic structure, the data type we
want the structure to work with is included after the name and between
Primitive Type Structures The structures for these data types were already defined for us in the Swift
Standard Library. All we need to do to get an instance is to call its 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 These initializers create a structure for each value and assign the instances
by Swift for the initializer variable = Int(value). Every time we assign a new to the variables. This is the same as assigning the values directly to the
value to a variable of a primitive data type, we are assigning a structure variable (e.g., var myprice = 4.99). There is no advantage in using initializers for
that contains that value. The following are the initializers of some of the primitive data types except when the value provided is of a different type.
primitive data types studied in Chapter 2. The definitions of these structures include several initializers that convert
the value to the right type. This is usually called casting, and we can use it
Int(Value)—This is the initializer of the Int data type. The argument is to turn a variable of one data type into another. For example, when we
the value we want to assign to the instance. If no value is provided, divide numbers, the system converts those numbers to the right type and
the value 0 is assigned by default. Initializers for similar types are also performs the operation, but variables are already of a specific type and
available (Int8(), Int16(), Int32(), and Int64()). therefore they must be explicitly converted before the operation is
UInt(Value)—This is the initializer of the UInt data type. The performed or we get an error (The process does not really convert the
argument is the value we want to assign to the instance. If no value is variable; it just creates a new value of the right type).
provided, the value assigned is 0. Initializers for similar types are also
available (UInt8(), UInt16(), UInt32(), and UInt64()). Listing 3-51: Casting a variable

Float(Value)—This is the initializer of the Float data type. The
var number1: Int = 10
argument is the value we want to assign to the instance. If no value is var number2: Double = 2.5
provided, the value 0.0 is assigned by default. var total = Double(number1) / number2 // 4.0

Double(Value)—This is the initializer of the Double data type. The
argument is the value we want to assign to the instance. If no value is The variables number1 and number2 defined in Listing 3-51 are of type Int
provided, the value assigned is 0.0. and Double. To perform a division between them we must cast one of them
to the data type of the other (arithmetic operations cannot be performed

on values of different data type). Using the Double() initializer, we create a max—This type property returns the maximum value the data type
new value of type Double from the value of number1 and perform the can handle.
operation (the value 10.0 created by the initializer is divided by the value random(in: Range)—This type method returns a random number.
2.5 of number2 to get the result 4.0). The process is described as "casting the The value is calculated from a range of integers provided by the in
number1 variable to a Double".
argument.
These initializers are also useful when working with String values.
Sometimes the characters of a string represent numbers that we need to negate()—This method inverts the sign of the value.
process. The problem is that strings cannot be processed as numbers. We isMultiple(of: Int)—This method returns true if the value is a
cannot include a string in an arithmetic operation without first converting multiple of the value provided by the of argument (this is similar to
the string into a value of a numeric data type. Fortunately, the initializers what we can achieve with the % operation).
for numeric types such as Int and Double can convert a value of type String
into a number. If the operation cannot be performed, the initializer returns The min and max properties are especially useful because they allow us
nil, so we can treat it as an optional value. In the following example, we to determine whether an operation could overflow a variable (produce a
convert the string "45" into the integer 45 and add the value 15 to it. result that is greater or lesser than the minimum and maximum allowed).

Listing 3-52: Extracting numbers from strings Listing 3-53: Checking the maximum possible value for the Int8 type
 
var units = "45" var mynumber: Int8 = 120
let increment: Int8 = 10
if let number = Int(units) {
let total = number + 15 if (Int8.max - mynumber) >= increment { // (127 - 120) >= 10
print("The total is \(total)") // "The total is 60" mynumber += increment
} }
 print(mynumber) // "120"

The structures defined for primitive data types also have their own
properties and methods. This includes type properties and methods. For This example takes advantage of the max property to make sure that
instance, the following are the most frequently used properties and incrementing the value of a variable will not overflow the variable (the
methods provided by the structures that process integer values (e.g., Int). result will not be greater than the maximum the variable can handle). The
code starts by defining a variable of type Int8 to store the result of the
min—This type property returns the minimum value the data type operation and another to store the number we want to add. Then, we
can handle. calculate how far the current value of mynumber is from the maximum value
admitted by an Int8 variable (Int8.max – mynumber) and compare this result
with the value of increment. If the number of units we have left is greater or
equal than the value of increment, we know that the operation can be Listing 3-54: Rounding floating-point values
performed without going over the limit (in this example, the operation is 
not performed because the addition of 120 + 10 produces a result greater var mynumber: Double = 2.890
mynumber = mynumber.rounded(.toNearestOrAwayFromZero)
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 
methods. The following are the most frequently used.
Of course, Boolean values are also structures. Among others, the Bool
pi—This type property returns the value of the constant pi. data type offers the following methods.
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 changes
random(in: Range)—This type method returns a random number. the value to false if it is true.
The value is calculated from a range of values of type Double provided
by the in argument. Listing 3-55: Changing 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. 

rounded(FloatingPointRoundingRule)—This method returns the


value rounded according to the rule specified by the argument. The
argument is an enumeration with the values awayFromZero, down,
toNearestOrAwayFromZero, toNearestOrEven, towardZero and up.

In this case, the most useful method is probably rounded(). With this
method, we can round a floating-point value to the nearest integer.

Range Structures contains(Element)—This method returns a Boolean value that


determines if the value specified by the argument is inside the range.

clamped(to: Range)—This method compares the original range
The random() method provided by some of the structures introduced above with the range specified by the to argument and returns a new range
work with ranges of values (collections of values in sequential order). with the part of the ranges that overlap.
These are structures included in the Swift Standard Library that can reversed()—This method returns a collection with the values in
manage open and close ranges of values. For instance, we can create a
reversed order.
range between 1 and 5. If the range is open, it will include the values 1, 2,
3, and 4, but if the range is closed, it will include the values 1, 2, 3, 4, and 5.
Ranges are useful in a variety of situations. For instance, if we need a loop
Swift includes two operators to generate ranges.
with a fixed number of cycles, we can implement a for in loop with a range.
The following example iterates over a closed range of integers from 0 to 10,
... (three dots) creates a range from the value on the left to the
generating a total of 11 cycles.
value on the right, including both values in the range (e.g., 1...5
creates a range that includes the values 1, 2, 3, 4 and 5). The value Listing 3-56: Using for in to iterate over a range
on the right can be ignored to create a one-sided range. A one-

sided range goes from the value on the left to the maximum value
var total = 0
allowed for the type. for value in 0...10 {
..< (two dots and the less than character) creates a range from the total += value
}
value on the left to the value before the value on the right (e.g., 1.. print("The total is \(total)") // "The total is 55"
<5 creates a range that includes the values 1, 2, 3 and 4). 

When we declare a range using these operators, Swift creates the proper Of course, we can also invert that loop.
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 of
calls the reversed() method to invert it. This method creates a collection with primitive data types to get a random value. The following example
the values in reverse order, so we can read it with a for in loop. The generates a loop that calculates multiple random values from 1 to 10. The
statement inside the loop adds the values to the message string, and this condition stops the loop when the number returned by the method is
string is printed at the end to confirm that the values were effectively equal to 5. Inside the loop, we also increment the value of the attempts
reversed. variable to calculate the number of cycles required for the random() method
Ranges can also simplify switch statements that have to consider multiple to return our number.
values per case.
Listing 3-59: Calculating random numbers
Listing 3-58: Using range operators in a switch statement 
 var mynumber: Int = 0
var age = 6 var attempts = 0
var message = "You have to go to "
while mynumber != 5 {
switch age { mynumber = Int.random(in: 1...10)
case 2...4: attempts += 1
message += "Day Care" }
case 5...11: print("It took \(attempts) attempts to get the number 5")
message += "Elementary School" 
case 12...17:
message += "High School"
case 18..<22:
message += "College"
case 22...:
message += "Work"
default:
message += "Breastfeeding"
}
print(message) // "You have to go to Elementary School"

In this example, we compare an age with several ranges of ages. If the


value of age is within one of the ranges, the instructions for that case are
executed. As illustrated by this example, we can also declare only one side
of a range and let the system determine the other. The last case creates a
one-sided close range from the value 22 to the maximum value allowed for
the type.

String Structures uppercased()—This method returns a copy of the string in


 uppercase letters.
hasPrefix(String)—This method returns a Boolean value that
Not only primitive data types and ranges are structures, but also the rest of indicates whether the string begins with the text specified by the
the data types defined in the Swift Standard Library, including the String argument or not.
data type. As we have seen in Chapter 2, we can initialize a String structure hasSuffix(String)—This method returns a Boolean value that
by simply assigning a string (a text between double quotes) to a constant indicates whether the string ends with the text specified by the
or a variable. This is another shortcut. In the background, instances of the argument or not.
String structure are created from the initializer included in the structure’s
contains(Character)—This method returns a Boolean value that
definition. indicates whether the character specified by the argument exists in
the string or not.
String(Value)—This initializer creates a string from the value
provided by the argument. The String structure defines multiple Most of the time, we will assign a string directly to a variable as we have
versions of this initializer to create strings from different types of done so far, but the String() initializer may be useful when we need to
values, including other strings, characters, and numbers. convert values into strings. For instance, the following example converts
the number 44 into a string and counts the number of characters.
Once the string is created, we can manipulate it with the properties and
methods provided by the String structure. The following are the most Listing 3-60: Converting a number into a string
frequently used. 
var age = String(44)
isEmpty—This property returns a Boolean that indicates whether the var mytext = "Total digits \(age.count)" // "Total digits 2"

value is an empty string. This is the same as comparing the string with
an empty string (string == ""). Swift strings are composed of Unicode characters, which occupy different
count—This property returns the total number of characters in the amounts of memory, even when the characters look the same. Because of
string. this, it is not possible to establish the position of a character using integer
first—This property returns the first character in the string. values. The index of the first character is always 0, but the index of the
consecutive characters depends on the size of their predecessors. Swift
last—This property returns the last character in the string.
solves this problem by defining a data type called Index. This is another
lowercased()—This method returns a copy of the string in lowercase
structure defined inside the String structure designed to manage string
letters.
indexes. The String structure includes properties and methods to work with
these indexes and access the characters. The following are the most replaceSubrange(Range, with: String)—This method replaces the
frequently used. characters in the position determined by the range provided as the
first argument with the string provided by the with argument.
startIndex—This property returns the index value of the first removeSubrange(Range)—This method removes the characters in
character of the string. the positions determined by the range specified by the argument.
endIndex—This property returns the index value of one position
after the last character of the string. It is useful to manipulate range of Strings are collection of values. To access a specific character in a string, we
characters, as we will see later. must declare the Index structure with the index of the character we want to
firstIndex(of: Character)—This method returns the index where read after the name of the variable that contains the string, enclosed in
the character specified by the of argument appears for the first time square brackets, as in the following example.
in the string.
Listing 3-61: Processing the string’s characters
lastIndex(of: Character)—This method returns the last index where

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

the string the value of the contentsOf argument at the position
determined by the at argument. The first thing we do in this example is to check the value of the isEmpty
remove(at: Index)—This method removes and returns the property to make sure the string is not empty and there are characters to
character at the position determined by the at argument. read (notice the ! operator to invert the condition). Once we know we can
prefix(through: Index)—This method returns a string created from proceed, we get the index of the string’s first character from the startIndex
the first character of the original string to the character at the index property and read the character in that position using square brackets.
indicated by the through argument. If we want to access a character in a different position, we must increment
prefix(upTo: Index)—This method returns a string created from the the value returned by startIndex. The trick is that, since Index values are not
first character of the original string to the character at the index integers, we cannot just add a number to them. Instead, we must use the
indicated by the upTo argument, but without including this last methods provided by the String structure.
character.

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"


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

index(Index, offsetBy: Int)—This method increments the index


Once the right index is calculated, we can call some of the String methods
specified by the first argument the amount of units specified by the
to insert or remove characters. The insert() method, for instance, inserts a
offsetBy argument and returns a new Index value with the result. single character at the position indicated by its second argument. In the
following example, we call it with the value of endIndex to add a character at
The following example advances the initial index 6 positions to get a the end of the string (endindex points to the position after the last
different character. character).

Listing 3-62: Calculating a specific index Listing 3-64: Inserting a character in a string
 
var text = "Hello World" var text = "Hello World"
if text != "" { text.insert("!", at: text.endIndex)
let start = text.startIndex
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
with the index() method. The value returned by this method is an optional
The index() method applied in Listing 3-62 takes an integer to calculate the containing the Index value of the first character that matches the argument
new index. The original index is increased the number of units indicated by or nil if no character is found. In the following example, we implement it to
the integer and the resulting Index value is returned. With this index, we get find the first space character and remove it with the remove() method.
the character at the position 6 (indexes start from 0).
If we wanted to get the previous index, we could have specified a negative Listing 3-65: Removing a character
number of units for the offset value, but another way to move forward and 
backward is to implement the other versions of the index() method. The var text = "Hello World"
var findIndex = text.firstIndex(of: " ")
following example gets the next index after the initial index and prints the
corresponding character on the screen. if let index = findIndex {
text.remove(at: index)
print("New string is \(text)") // "New string is HelloWorld" findIndex = text.firstIndex(of: " ")
} if let start = findIndex {
 text.removeSubrange(start...) // "Goodbye"
}

If we want to work with groups of characters, we must implement ranges
of Index values.
The replaceSubrange() method in Listing 3-67 replaces the characters from the
beginning of the string up to the character before the space character
Listing 3-66: Getting a range of characters
("Hello") with the string "Goodbye", and the removeSubrange() method uses

var text = "Hello World"
an open range to remove the characters of this sentence from the space
var start = text.startIndex character to the end of the string (" World"), getting the final string
var findIndex = text.firstIndex(of: " ")
"Goodbye". Notice that after applying the methods over the same string,
if let end = findIndex { the indexes are lost and therefore they must be recalculated. That is why
print("First word is \(text[start..<end])") //"First word is Hello"
} before calling the removeSubrange() method we search for the position of the
 space character once more and update the findIndex variable.
The rest of the methods provided by the String structure are
The firstIndex() method in Listing 3-66 looks for a space character and
straightforward. For instance, the following example implements two of
returns its index. With this value, we can create a range from the first
them to check if a string contains the word "World" at the end and
character to the space character and get the first word. But we must be
converts all the letters into uppercase letters.
careful because the end index is pointing to the space character, not to the
last character of the word. To get the word without the space, we create an
open range with the..< operator, so the character on the right is not Listing 3-68: Implementing String methods

included.
let text = "Hello World"
We can also use ranges to replace or remove parts of the text. The String
structure offers the replaceSubrange() and removeSubrange() methods for this if text.hasSuffix("World") {
purpose. print(text.uppercased()) // "HELLO WORLD"
}

Listing 3-67: Working with ranges of characters

var text = "Hello World"
var start = text.startIndex
var findIndex = text.firstIndex(of: " ")

if let end = findIndex {


text.replaceSubrange(start..<end, with: "Goodbye") // "Goodbye World"
}

Array Structures frequently used is declaring the array with initial values enclosed in square
 brackets and separated by comma.

Listing 3-69: Declaring arrays


The strings studied before and the values we have created in previous

examples with functions such as stride() or repeatElement() are collections of
var list: [Int] = [15, 25, 35]
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. As with any other variable, Swift may infer the type from the values.
Swift includes several collections like this, some were defined to contain
specific values, like String, and others are generic (they are defined with a Listing 3-70: Declaring arrays with type inference
generic data type that can be turn into any other data type we need). One 

of those generic data types collections is Array. var list = [15, 25, 35]

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
The list array declared in these examples was initialized with three integer
any data type we want, but with the condition that once a data type is
values, 15, 25 and 35. The values of an array are usually called elements or
selected, all the values must be of that same type. For example, if we
items. On these terms, we can say that the code of Listing 3-70 declares an
create an array of type Int, we will only be able to store values of type Int in
array of three elements of type Int.
it. Swift offers multiple syntaxes to create an array, including the following
An index is assigned to each value automatically, starting from 0, and as
initializers.
with strings, we must specify the index of the value we want to read
surrounded by square brackets.
Array<Type>()—This initializer returns an empty Array structure of
the data type indicated by the value of Type. Listing 3-71: Reading the array’s elements
Array(repeating: Value, count: Int)—This initializer returns an 

Arraystructure with copies of the same value. The repeating var list = [15, 25, 35]
print(list[1]) // 25
argument determines the value to copy, and the count argument

determines how many copies the array will contain.
The last statement of Listing 3-71 prints the value of the second element of
A shortcut to create an array is to declare the data type between square
the list array (the element at index 1) on the console. We can also use
brackets followed by parentheses (e.g., var list = [Int]()), but the most
indexes to modify the values of an array.
Listing 3-72: Assigning a new value to an element Listing 3-75: Creating multidimensional arrays
 
var list = [15, 25, 35] var list: [[Int]] = [[2, 45, 31], [5, 10], [81, 12]]
list[0] = 400 
print(list) // [400, 25, 35]

This example creates an array of arrays of integers (notice the declaration
Assigning new values is only possible for elements that already exist in the of the array inside another array [[Int]]). To access the values, we must
array. To add a new element, or several, we can use the += operator. declare the indexes of each level in square brackets, one after another. The
following example returns the first value (index 0) of the second array
Listing 3-73: Adding new elements to an array (index 1). The instruction looks for the array at index 1 and then gets the
 number at index 0.
var list = [15, 25, 35]
list += [45, 55] Listing 3-76: Reading values from a multidimensional array
print(list) // [15, 25, 35, 45, 55]


var list: [[Int]] = [[2, 45, 31], [5, 10], [81, 12]]
print(list[1][0]) // 5
The += operator adds an array at the end of another array. In Listing 3-73, 
we use it to add two more elements to the array declared in the first
statement. What the += operator does is to concatenate two arrays and To remove all the elements from an array, we can assign to the variable
one of the initializers introduced before or just square brackets with no
assign the result back to the same variable. If we want to use two or more
values.
arrays to create a new one, we can apply the + operator.
Listing 3-77: Removing the elements of an array
Listing 3-74: Concatenating two arrays 

var list = [15, 25, 35]
var list1 = [15, 25, 35] list = []
var list2 = [45, 55, 65] 
var final = list1 + list2 // [15, 25, 35, 45, 55, 65]

Arrays are collections of values and therefore we can iterate over their
It is possible to declare arrays of arrays. These types of arrays are called values with a for in loop, as we did with strings before.
multidimensional arrays. Arrays inside arrays are listed separated by
Listing 3-78: Reading an array with a for in loop
comma.

var total = 0 removeFirst()—This method removes the first element of the array.
let list = [15, 25, 35]
It returns the value of the element deleted.
for value in list {
total += value removeLast()—This method removes the last element of the array.
} It returns the value of the element deleted.
print("The total is \(total)") // "The total is 75"
 removeAll(where: Closure)—This method removes the elements
in the array that meet the condition established by the closure
The code in Listing 3-78 uses a for in loop to add the numbers of the list assigned to the where argument.
array to the total variable. At the end, we print the result. Although this is a removeSubrange(Range)—This method removes a range of
legit way to do it, arrays offer multiple properties and methods to read and elements from the array. The argument is a range of integers
process their values. representing the indexes of the elements to remove.
replaceSubrange(Range, with: Array)—This method replaces a
count—This property returns the total number of elements in the range of elements with the elements of the array provided by the
array.
with argument. The first argument is a range of integers
isEmpty—This property returns a Boolean value that indicates if the corresponding to the indexes of the elements we want to replace.
array is empty.
dropFirst(Int)—This method removes the number of elements
first—This property returns the first element of the array or nil if the specified by the argument from the beginning of the array. If no
array is empty. amount is declared, only the first element is removed.
last—This property returns the last element of the array or nil if the dropLast(Int)—This method removes the number of elements
array is empty. specified by the argument from the end of the array. If no amount is
append(Element)—This method adds the value specified by the declared, only the last element is removed.
argument at the end of the array. enumerated()—This method is used to iterate over the elements of
insert(Element, at: Int)—This method adds a new element in a the array. It returns a tuple containing the index and the value of the
specific position of the array. The first argument is the value we want current element.
to assign to the new element, and the at argument represents the min()—This method compares the values of the elements and
position of the array where we want to insert the element. returns the smallest.
remove(at: Int)—This method removes an element from the array max()—This method compares the values of the elements and
at the index specified by the at argument. returns the largest.
sorted()—This method returns an array with the elements of the first argument is the value that is going to be processed with the first
array in ascending order. value of the array.
sorted(by: Closure)—This method returns an array with the contains(where: Closure)—This method returns a Boolean that
elements of the array in the order determined by the closure provided determines if the array contains an element that meets the condition
to the by argument. in the closure.
randomElement()—This method randomly selects an element from allSatisfy(Closure)—This method returns a Boolean value that
the array and returns it. If the array is empty, the value returned is nil. determines if all the elements of the array comply with the requisites
shuffled()—This method returns an array with the elements of the of a closure.
array in random order. difference(from: Array)—This method returns a CollectionDifference
reversed()—This method returns an array with the elements of the structure containing all the changes that has to be performed to
array in reverse order. synchronize the array with the array provided by the from method.
This method can work in conjunction with the applying() method to
swapAt(Int, Int)—This method exchanges the values of the
apply all the changes in the array at once.
elements at the indexes specified by the arguments.
joined(separator: String)—This method returns a string that In the previous example, we have seen how to iterate over the elements of
includes all the values in an array of strings joined by the string an array with the for in loop, but that iteration only returns the value of the
specified by the separator argument. element, not its index. An alternative is provided by the enumerated()
filter(Closure)—This method filters an array and returns another method, designed to work with these types of loops. Each cycle returns a
array with the values that passed the filter. The argument is a closure tuple with the index and the value of the current element.
that processes the elements and returns a Boolean indicating whether
the value passed the test or not. Listing 3-79: Reading indexes and values of an array

map(Closure)—This method returns a new array containing the
let fruits = ["Banana", "Orange", "Apple"]
results of processing each of the values of the array. var message = "My fruits:"
compactMap(Closure)—This method returns a new array
for (myindex, myfruit) in fruits.enumerated() {
containing the results of processing each of the values of the array, message += " \(myindex + 1)-\(myfruit)"
but ignores the values that produce a nil result. }
print(message) // "My fruits: 1-Banana 2-Orange 3-Apple"
reduce(Value, Closure)—This method sends the values of the array 

to the closure one by one and returns the result of the operation. The
This example uses the constants myindex and myfruit to capture the values
produced by the enumerated() method and generates a string. Notice that

since the array’s indexes start from 0, we added 1 to myindex to start and the index 1 to the third element. The system makes sure that
counting from 1. the indexes are always consecutive and start from 0.
Another useful property is count. As mentioned before, we can access each
element of the array with the index between square brackets. But trying to A more complex method is removeAll(where:). This method removes several
read a value in an index that has not yet been defined will return an error. elements at once, but only those that meet a condition. The condition is
To make sure that the index exists, we can check whether it is greater than established by a closure that processes each of the values in the array and
returns true or false depending on whether the value meets the condition or
0 and less than the total amount of elements in the array using this
not. In the following example, we compare each value with the string
property.
"Orange" and therefore all the values "Orange" are removed from the
array.
Listing 3-80: Checking whether an array contains a value in a specific index

Listing 3-82: Removing all the elements that meet a condition
let ages = [32, 540, 12, 27, 54]
let index = 3 
if index > 0 && index < ages.count { var fruits = ["Banana", "Orange", "Apple", "Orange"]
print("The value is: \(ages[index])") // "The value is: 27" fruits.removeAll(where: { (value) in
} value == "Orange"
 })
print(fruits) // ["Banana", "Apple"]

The methods to add and remove elements from an array are
straightforward. The following example illustrates how to implement them.
Another method that work with a closure is contains(where:). In the following
Listing 3-81: Adding and removing elements example, we use this method to determine whether an array contains a
 value greater than 60 or not.
var fruits = ["Banana", "Orange"]
if !fruits.isEmpty { Listing 3-83: Finding if an element meets a condition
fruits.append("Apple") // ["Banana", "Orange", "Apple"]

fruits.removeFirst() // "Banana"
fruits.insert("Pear", at: 1) // ["Orange", "Pear", "Apple"] var list = [55, 12, 32, 5, 9]
fruits.insert(contentsOf: ["Cherry", "Peach"], at: 2) let found = list.contains(where: { (value) in
// ["Orange", "Pear", "Cherry", "Peach", "Apple"] value > 60
} })
 print(found) // false

IMPORTANT: Every time an array is modified, its indexes are


We can also select a random value with the randomElement() method. This
reassigned. For instance, if you remove the first element of an array
method selects a value from the array and returns an optional, so we must
of three elements, the index 0 is reassigned to the second element
compare it against nil or use optional binding before processing it, as in the Arrays created from a range of indexes are of type ArraySlice. This is another
following example. collection type provided by Swift to store temporary arrays that are
composed of elements taken from other arrays. We can iterate over these
Listing 3-84: Selecting a random value from an array 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
let fruits = ["Banana", "Orange", "Apple"]
if let randomValue = fruits.randomElement() { for persistent storage, we must cast them as Array types using the Array()
print("The selected value is: \(randomValue)") initializer. The initializer takes the values of the ArraySlice variable and
}
 returns a normal array, as shown next.

Another random operation is performed by the shuffled() method. With this Listing 3-87: Casting arrays of type ArraySlice
method we can randomly sort the elements of an array. 
var fruits = ["Banana", "Orange", "Apple", "Cherry"]
var someFruits = fruits[0..<2] // ["Banana", "Orange"]
Listing 3-85: Changing the order of the elements of an array var newArray = Array(someFruits)
 
var fruits = ["Banana", "Orange", "Apple"]
fruits = fruits.shuffled() The Array structure also offers the removeSubrange() and replaceSubrange()
print(fruits) // e.g.: ["Orange", "Apple", "Banana"]
methods to remove and replace a range of elements.

Besides working with all the elements of an array, we can do it with a range Listing 3-88: Removing and replacing elements
of elements. 
var fruits = ["Banana", "Orange", "Apple", "Banana", "Banana"]
fruits.removeSubrange(1...2)
Listing 3-86: Reading a range of elements fruits.replaceSubrange(0..<2, with: ["Cherry", "Cherry"])
 print(fruits) // "["Cherry", "Cherry", "Banana"]"

var fruits = ["Banana", "Orange", "Apple", "Cherry"]
var someFruits = fruits[0..<2] // ["Banana", "Orange"]
In Listing 3-88, we call the removeSubrange() method to remove the range of
print("The new selection has \(someFruits.count) fruits")

elements from index 1 to 2, getting an array filled with the value "Banana",
and then we call the replaceSubrange() method to replace the elements from
This example gets the elements at the indexes 0 and 1 from the fruits array index 0 to 1 with another array filled with "Cherries". This is just to
and assigns them to the new someFruits array. Now we have two arrays: fruits illustrate how the methods work, but it shows a recurrent situation in app
with 4 elements and someFruits with 2. development where sometimes we need to fill a collection with elements
of the same value. When working with arrays, this is easy to achieve. The

Array structure includes an initializer that takes two arguments, repeating and value "Grape", and returns a Boolean with the result. If the value is true, the
count, and generates an array with the number of elements indicated by element is included in filteredArray.
count and the value indicated by repeating. 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
Listing 3-89: Initializing an array with elements of the same value array one by one and returns another array with the results produced by
 the closure.
var fruits = ["Banana", "Orange", "Apple"]
Listing 3-91: Mapping an array
let total = fruits.count
let newArray = Array(repeating: "Cherry", count: total) 
fruits.replaceSubrange(0..<total, with: newArray) let list = [2, 4, 8, 16]
let half = list.map({ $0 / 2 })
print(fruits) // "["Cherry", "Cherry", "Cherry"]" print(half) // "[1, 2, 4, 8]"
 

In this example, we create an array with the same amount of elements as The example in Listing 3-91 defines a list of integers and then calls the map()
the fruits array and then use the replaceSubrange() method to replace every method on the array to divide each value by 2. The map() method sends the
element with a new one. values of the array to the closure one by one, the closure replaces the
The methods to remove and replace elements of an array are not selective placeholder ($0) with the current value, divides the number by 2, and
enough; they affect the elements in a specific index or a range of indexes returns the result. All the results are stored in a new array and that array is
without considering their values. If we want to perform a more specific job, returned by the map() method when the process is over.
we must use the filter() method. This method takes a closure and sends Of course, we can perform any kind of operations on the values in the
each element to the closure for processing. If the closure returns true, the closure. For instance, the following code converts the values into strings
element is included in the new array, otherwise it is ignored, as shown with the String() initializer.
next.
Listing 3-92: Converting the elements of an array into strings
Listing 3-90: Filtering the values of an array 
 let list = [1, 2, 3, 4, 5]
var fruits = ["Apple", "Grape", "Banana", "Grape"] let listtext = list.map({ String($0) })
var filteredArray = fruits.filter({ $0 != "Grape" }) print(listtext) // "["1", "2", "3", "4", "5"]"
print(filteredArray) // "["Apple", "Banana"]" 

When all we want to do is to initialize a new structure with the value
The filter() method sends the values one by one to the closure, the closure received by the closure, instead of a closure, Swift allows us to provide the
replaces the placeholder ($0) with the current value, compares it with the structure's initializer. The value received by the closure is sent to the
initializer and a new structure of that type is returned.
returns a new array with the same elements in reversed order. The value
Listing 3-93: Using a structure initializer with the map() method returned by the method is stored in a structure of type ReversedCollection. As
 we did before with the ArraySlice type, we can cast these values as Array
let list = [1, 2, 3, 4, 5] structures with the Array() initializer.
let listtext = list.map(String.init)
print(listtext) // "["1", "2", "3", "4", "5"]"
 Listing 3-95: Reversing the elements of an array

This example produces the same result as before, but instead of using a var fruits = ["Apple", "Blueberry", "Banana"]
closure, we use a reference to the String initializer. The map() method sends var array = Array(fruits.reversed()) // ["Banana", "Blueberry", "Apple"]

the values to the initializer, the initializer returns a new String structure with
that value, and the process continues as always. The sorted() method sorts the array in ascending order and returns a new
Another way to process all the values of an array at once is with the reduce()
array.
method. This method works in a similar way than map(), but instead of
storing the results in an array, it sends the result back to the closure to get
Listing 3-96: Sorting the elements of an array
only one value in return. For instance, the following code uses the reduce() 
method to get the result of the addition of all the numbers in an array.
var fruits = ["Blueberry", "Apple", "Banana"]
let basket = fruits.sorted()
Listing 3-94: Reducing an array print(basket) // ["Apple", "Banana", "Blueberry"]


let list = [2, 4, 8, 16]
let total = list.reduce(0, { $0 + $1 })
If we want to sort the elements in a custom order, we can use the sorted(by:)
print(total) // "30" method. This method works in a similar fashion to the filter() method
 studied before. It takes a function or a closure that receives the value of
two elements and returns true if the first element should appear before the
The code in Listing 3-94 defines an array of integers and then calls the
second element, or false otherwise.
reduce() method on it. This method sends two values at a time to the
closure. In the first cycle, the values sent to the closure are the ones Listing 3-97: Sorting the elements of an array in a custom order
provided by the first argument (0) and the first value of the array (2). In the

second cycle, the values sent to the closure are the value returned by the
var fruits = ["Apple", "Raspberry", "Banana", "Grape"]
closure in the first cycle (0 + 2 = 2), and the second value of the array (4). var newArray = fruits.sorted(by: { $0 > $1 })
The loop goes on until all the values of the array are processed. print(newArray[0]) // "Raspberry"

When it comes to sorting the elements of an array, there are several
options available. The most frequently used are reversed() and sorted() (and its
variant sorted(by:)). The reversed() method takes the elements of an array and

When the sorted() method is executed, it performs a loop. On each cycle,


if let older = ages.max() {
two values of the fruits array are sent to the closure. The closure compares let digits = String(older)
the values and returns true or false accordingly. This indicates to the sorted() print("The maximum age is \(digits.count) digits long")
}
method which value should appear before the other in the new array, 
effectively sorting the elements. Unlike the example we programmed for
the filter() method, this one does not compare the argument against a The code in Listing 3-100 takes the largest value from an array of integers
specific value. This allows us to order arrays of any data type. For example, and counts the number of digits in the returned value. Because the max()
we can use the closure to sort an array of integers. method returns an optional, we use optional binding to get its value. The
rest of the code turns this value into a string and counts its characters to
Listing 3-98: Sorting an array of integers print the number of digits on the console.
 Besides selecting the largest or smallest value with the max() and min()
var numbers = [55, 12, 32, 5, 9] methods, we can also fetch values from the array using the first and last
var newArray = numbers.sorted(by: { $0 < $1 })
print(newArray[0]) // 5 properties.

Listing 3-101: Getting the first value of an array
If we decide to work with specific data types, we can perform custom 
tasks. For example, we can count the characters in the strings and sort let ages = [32, 540, 12, 27]
them according to their length.
if let firstAge = ages.first {
print("The first person is \(firstAge) years old") // 32
Listing 3-99: Sorting strings according to the number of characters }
 

var fruits = ["Apple", "Blueberry", "Banana", "Grape"]


var newArray = fruits.sorted(by: { $0.count < $1.count }) The value returned by the first property is also an optional, so we use
print(newArray) // ["Apple", "Grape", "Banana", "Blueberry"]

optional binding again to get its value and store it in the firstAge constant.
The first and last properties only get the first and last value, respectively. To
Arrays also include two powerful methods to compare elements: min() and search for any value in the array or its index, the Array structure offers the
max(). These methods compare the values and return the smallest or following methods.
largest, respectively.
firstIndex(of: Element)—This method performs a search from the
Listing 3-100: Getting the largest element beginning of the array and returns the index of the first element that
 matches the value of the of argument.
let ages = [32, 540, 12, 27]
lastIndex(of: Element)—This method performs a search from the Listing 3-103: Getting the index of a value that meets a condition
end of the array and returns the index of the first element that 

matches the value of the of argument. let ages = [32, 540, 12, 27, 54]
let first = ages.firstIndex(where: { $0 < 30 })
firstIndex(where: Closure)—This method returns the index of the if first != nil {
print("The first value is at index \(first!)") // 2
first value that meets the condition in the closure assigned to the }
where argument. 

lastIndex(where: Closure)—This method returns the index of the


In this example, we look for the index of a value smaller than 30. The
last value that meets the condition in the closure assigned to the where
firstIndex(where:) method reads every value of the array from the beginning
argument.
and sends them to the closure assigned to the where argument. This closure
first(where: Closure)—This method returns the first value that assigns the current value to the placeholder and compares it against the
meets the condition in the closure assigned to the where argument. number 30, if the value is greater than 30, the closure returns false,
last(where: Closure)—This method returns the last value that otherwise it returns true and the index of that value is assigned to the first
meets the condition in the closure assigned to the where argument. 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.
If we only need the index of a particular element, we can use the
firstIndex(of:) method. For instance, we can look for the first appearance of a
number in an array and get the index.

Listing 3-102: Getting the index of a specific value



let ages = [32, 540, 12, 27, 54]

if let index = ages.firstIndex(of: 540) {


print("The value is at the position \(index)") // 1
}

If what we need instead is to get the index of a value that meets certain
condition, we can use methods like firstIndex(where:) or lastIndex(where:)
depending on whether we want to search from the beginning or the end of
the array.

Set Structures To process the elements of a set, we can use a for in loop, as we did
before with strings and arrays, but sets also provide their own properties

and methods for this purpose.
If we store two elements in an array, one element will automatically
receive the index 0 and the other the index 1. This correlation between
count—This property returns the number of elements in the set.
indexes and values never changes, allowing elements to be listed always in isEmpty—This property returns a Boolean that indicates whether the
the right order and have elements with the same value at different indexes. set is empty or not.
Sets are like arrays, but they do not assign an index to their values and contains(Element)—This method returns a Boolean that indicates
therefore there is no order or duplicated values. Sets are created from the whether there is an element in the set with the value specified by the
Set structure. argument.
contains(where: Closure)—This method returns a Boolean that
Set<Type>()—This initializer returns an empty Set structure of the
determines if the set contains an element that meets the condition in
data type indicated by the value of Type.
the closure.

This initializer can be used to create an empty set (e.g., let myset = Set<Int>()), min()—This method compares the elements in the set and returns
but we can also use square brackets, as we do with arrays. The difference the smallest.
with arrays is that we must declare that we are creating a set with the Set max()—This method compares the elements in the set and returns
keyword, as shown next. the largest.
sorted()—This method returns an array with the elements of the set
Listing 3-104: Creating an empty set of integers in ascending order.

var ages: Set<Int> = []
sorted(by: Closure)—This method returns an array with the
 elements of the set in the order determined by the closure specified
by the by argument.
If we initialize the set with some values, Swift can infer its type from the randomElement()—This method randomly selects an element from
values’ data type, simplifying the declaration. the set and returns it. If the set is empty, the value returned is nil.

Listing 3-105: Creating a set of integers


shuffled()—This method returns an array with the elements of the

set in random order.
var ages: Set = [15, 25, 35, 45] insert(Element)—This method inserts a new element in the set with
 the value provided by the argument.
union(Collection)—This method returns a new set created with the }

values of the original set plus the values provided by the argument
(an array or another set).
To insert a new element, we just have to execute the insert() method.
subtract(Collection)—This method returns a new set created by
subtracting the elements provided by the argument to the original Listing 3-107: Inserting a new element
set. 
intersection(Collection)—This method returns a new set created var fruits: Set = ["Apple", "Orange", "Banana"]

with the values of the original set that match the values provided by if !fruits.contains("Grape") {
the argument (an array or another set). fruits.insert("Grape")
}
remove(Element)—This method removes from the set the element print("The set has \(fruits.count) elements") // 4
with the value provided by the argument. 

isSubset(of: Set)—This method returns a Boolean that indicates


whether or not the set is a subset of the set specified by the of
argument.
isSuperset(of: Set)—This method returns a Boolean that indicates
whether or not the set is a superset of the set specified by the of
argument.
isDisjoint(with: Set)—This method returns a Boolean that indicates
whether or not the original set and the set specified by the with
argument have elements in common.

Using these methods, we can easily access and modify the values of a
set. For instance, we can implement the contains() method to search for a
value.

Listing 3-106: Using contains() to find an element in a set



var fruits: Set = ["Apple", "Orange", "Banana"]

if fruits.contains("Apple") {
print("Apple exists!")

In Listing 3-107, we use the contains() method again to check if an The rest of the methods available are straightforward. The following
element with the value "Grape" already exists in the set, but this is not example joins two sets with the union() method and then subtracts
really necessary. If the value is already part of the set, the insert() method elements from the result with subtract().
does not perform any action.
To remove an element we must call the remove() method. Listing 3-110: Combining sets

Listing 3-108: Removing an element from a set var fruits: Set = ["Apple", "Banana"]
var newSet = fruits.union(["Grapes"]) // "Banana", "Grapes", "Apple"

newSet.subtract(["Apple", "Banana"]) // "Grapes"
var fruits: Set = ["Apple", "Orange", "Banana"] 

if let removed = fruits.remove("Banana") {


print("\(removed) was removed") // "Banana was removed" The Set structure also offer methods to compare sets. We can
}

determine if a set is a subset or a superset of another set with the isSubset()
and isSuperset() methods or check if two sets have elements in common with
The remove() method removes the element which value matches the the isDisjoint() method. The following example implements the isSubset()
value of its argument and returns an optional with the value removed or nil method to check if the fruits in a basket come from the store. The code
in case of failure. In the code of Listing 3-108, we get the value returned by checks if the elements in the basket set are found in the store set and returns
true in case of success.
the method and print a message if it was removed successfully.
Sets are collections without order. Every time we read a set, the order in
Listing 3-111: Comparing sets
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
var store: Set = ["Banana", "Apple", "Orange", "Pear"]
example sorts the elements of the fruits set in alphabetical order, creating a var basket: Set = ["Apple", "Orange"]
new array we call orderFruits.
if basket.isSubset(of: store) {
print("The fruits in the basket are from the store")
Listing 3-109: Sorting the elements of a set }


var fruits: Set = ["Apple", "Orange", "Banana"]
var orderFruits = fruits.sorted()
if let lastItem = orderFruits.last {
print(lastItem) // "Orange"
}

Dictionary Structures The first value of each item is the key and the second is the value. Of
course, if the keys and the values are of a clear data type, Swift can infer
 them.

There is only one way to access the elements of an array and that is Listing 3-113: Declaring a dictionary with type inference
through their numeric indexes. Dictionaries offer a better alternative. With 
dictionaries, we can define the indexes ourselves using any custom value var list = ["First": "Apple", "Second": "Orange"]
we want. Each index, also known as key, must be explicitly declared along 
with its value. Swift offers multiple syntaxes to create a dictionary,
including the following initializers. 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<Type1: Type2>()—This initializer returns an empty
structure with the keys and values of the data type indicated
Dictionary
Listing 3-114: Assigning a new value to an element of a dictionary

by the value of Type1 and Type2.
var list = ["First": "Apple", "Second": "Orange"]
Dictionary(grouping: Collection, by: Closure)—This initializer list["Second"] = "Banana"

returns a Dictionary structure with the values provided by the grouping
argument grouped in arrays according to the keys returned by the
The second statement in Listing 3-114 assigns a new value to the element
closure provided by the by argument. identified with the keyword "Second". Now, the dictionary contains two
elements with the values "Apple" and "Banana". If the keyword used to
If the data types are explicitly defined, we can also declare an empty assign the new value exists, the system updates the value, but if the
dictionary with a simplified syntax, as in var list: [String: String] = Dictionary(), or keyword does not exist, a new element is created, as shown next.
use square brackets with a colon, as in var list: [String: String] = [:]. This
shortcut is also used to define a dictionary with initial values. In this case, Listing 3-115: Adding a new element to a dictionary
the keys and values are separated by a colon and the items are separated 

by comma, as in the following example. var list = ["First": "Apple", "Second": "Orange"]
list["Third"] = "Banana"
Listing 3-112: Declaring a dictionary with initial values print(list) // "["Second": "Orange", "First": "Apple", "Third": "Banana"]"
 

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


In this last example, the second statement assigns the value "Banana" to a

keyword that does not exist. The system creates the new element with the
specified keyword and value.

Dictionaries return optional values. If we try to read an element with a containing the key and the element’s value.
keyword that does not exist, the value returned is nil.
Listing 3-119: Using for in to iterate over a dictionary
Listing 3-116: Reading an element that does not exist 
 var fruits = ["First": "Apple", "Second": "Orange"]
var message = "My fruits:"
var list = ["First": "Apple", "Second": "Orange"]
print(list["Third"]) // nil
for (myindex, myfruit) in fruits {

message += " \(myindex)-\(myfruit)"
}
The code in Listing 3-116 tries to read a value with the keyword "Third" print(message) // "My fruits: First-Apple Second-Orange"

that does not exist in the list dictionary. As a result, the value nil is printed
on the console. If the element exists and we want to read its value, we The for in loop in Listing 3-119 reads the elements of the fruits dictionary
must unwrap it. one by one, assigns the index and the value to the myindex and myfruit
constants, and adds their values to the message variable, so at the end we
Listing 3-117: Reading the values
get a string with all the keys and values in the dictionary.

Of course, dictionaries may also contain arrays as values. The declaration is
var list = ["First": "Apple", "Second": "Orange"]
if let first = list["First"], let second = list["Second"] {
simple, the key is declared as always, and the single value is replaced by an
print("We have \(first) and \(second)") // "We have Apple and Orange" array.
}

Listing 3-120: Combining dictionaries with arrays

Since dictionary elements are optionals, we can assign the value nil to
var fruits: [String: [String]] = ["A": ["Apple", "Apricot"], "B": ["Banana", "Blueberries"]]
remove them. The following example removes the element with the

keyword "First".
Reading the values of a dictionary like this is a bit more complicated.
Listing 3-118: Removing an element from a dictionary Because dictionaries return optionals, we cannot just specify the indexes as
 we do for multidimensional arrays (see Listing 3-76). The value returned by
var list = ["First": "Apple", "Second": "Orange"] the dictionary must be unwrapped before accessing its values.
list["First"] = nil

Listing 3-121: Reading arrays inside dictionaries
As with arrays and sets, we can also iterate over the values of a dictionary 

with a for in loop. The value produced by each cycle of the loop is a tuple var fruits: [String: [String]] = ["A": ["Apple", "Apricot"], "B": ["Banana", "Blueberries"]]
if let list = fruits["A"] {
print(list[0]) // "Apple"
} isEmpty—This property returns a Boolean value that indicates if the

dictionary is empty.

In this example, we create a dictionary with two values. The values are keys—This property returns a collection with the keys in the
arrays of strings with a string as key. The code gets the array corresponding dictionary.
to the "A" key, unwraps it, and stores it in a constant. The list constant now values—This property returns a collection with the values in the
contains the array assigned to the "A" key, and therefore when we read the dictionary.
element at index 0 of that array, we get the value "Apple". sorted(by: Closure)—This method returns an array of tuples with
This is actually how the Dictionary(grouping:, by:) initializer works. It takes the each element of the dictionary (key and value) in the order
values of a collection and groups them together in arrays according to the determined by the closure.
value of a key returned by the closure, as in the following example. randomElement()—This method randomly selects an element from
the dictionary and returns a tuple with its key and value. If the
Listing 3-122: Grouping values by a key
dictionary is empty, the value returned is nil.

let list = [15, 25, 38, 55, 42]
shuffled()—This method returns an array of tuples containing the
let group5 = Dictionary(grouping: list, by: {$0 % 5 == 0 ? "Yes" : "No"}) keys and values of each element of the dictionary in random order.
print(group5) // "["No": [38, 42], "Yes": [15, 25, 55]]"
 updateValue(Value, forKey: Key)—This method updates the value
of an element with the value and key specified by its arguments. If the
The Dictionary initializer implemented in Listing 3-122 takes the values of the key does not exist, the method creates a new element. It returns the
list array, sends them to the closure one by one, and creates a new previous value if the key exists or nil otherwise.
dictionary with the keys returned by the closure. The closure receives the removeValue(forKey: Key)—This method removes the element
value and returns the strings "Yes" or "No" depending on whether the with the key equal to the value of the forKey argument. It returns an
current value is multiple of 5. If the value is multiple of 5, it is included in optional containing the value of the deleted element or nil if no
an array with the key "Yes", otherwise it is included in an array with the key element with the specified key was found.
"No". contains(where: Closure)—This method returns a Boolean value
Dictionaries incorporate plenty of functionality by themselves, but they that determines if the dictionary contains an element that meets the
also include properties and methods to manage their values. The following condition in the closure.
are the most frequently used.
Some of the methods provided by the Dictionary structure are like those
count—This property returns the total number of elements in the included in the Array and Set structures, but others are more specific. For
dictionary.

example, the updateValue() and removeValue() methods require the element's compare the values of the tuples at the index 1 ($0.1 < $1.1). The array
key to process the values. returned is a collection of tuples in alphabetical order, with every element
containing the keys and values of the dictionary ([(key: "two", value: "Apple"),
Listing 3-123: Adding and removing elements from a dictionary (key: "one", value: "Banana"), (key: "three", value: "Pear")]).

Earlier, we saw how to iterate over the elements of a dictionary with a for in
var fruits = ["one": "Banana", "two": "Apple", "three": "Pear"]
fruits.updateValue("Banana", forKey: "three") // "Pear" loop (see Listing 3-119). The loop gets each element and generates a tuple
fruits.removeValue(forKey: "one") // "Banana" with the key and value. But there are times when we only need the
print(fruits) // "["three": "Banana", "two": "Apple"]"
 element’s key or the element’s value. The Dictionary structure provides two
properties for this purpose: keys and values. The properties return a
The updateValue() method updates the value of an element when there is collection containing only the keys or the values of the elements,
already an element with that key or creates a new one if the key does not respectively.
exist. This is the same as assigning a value directly to an element (see
Listings 3-114 and 3-115), but the method returns the previous value, Listing 3-125: Iterating over the dictionary’s keys
which may be useful sometimes. 

Like sets, dictionaries are an unordered collection of values, but we can var fruits = ["one": "Banana", "two": "Apple", "three": "Pear"]

create an array with their elements in a specific order using the sorted() for key in fruits.keys {
method. The method returns the values as tuples, with the element’s key if key == "two" {
print("We have an element with the key ‘two’")
first and the value second. }
}

Listing 3-124: Sorting the values of a dictionary
 The collections returned by the keys and values properties are structures of
var fruits = ["one": "Banana", "two": "Apple", "three": "Pear"] type Keys and Values defined inside the Dictionary structure. As we did before
var list = fruits.sorted(by: { $0.1 < $1.1 })
print(list) with other collection types, to work with their values we can turn them
 into arrays with the Array() initializer.

As with arrays, the sorted() method sends to the closure two values at a Listing 3-126: Reading the keys of a dictionary
time, but the values in a dictionary are sent as tuples containing the key 
and value of each element. For instance, the first values sent to the closure var fruits = ["one": "Banana", "two": "Apple", "three": "Pear"]
in Listing 3-124 are ("one", "Banana") and ("two", "Apple"). These values let keys = Array(fruits.keys)
print(keys)
take the place of the placeholders $0 and $1, so if we want to order the 
elements according to their values (the names of the fruits), we must
3.4 Enumerations Listing 3-129: Declaring a variable of type Number


enum Number {
case one, two, three
Enumerations are a way to create data types with a limited set of values. }
var mynumber: Number = Number.one
An enumeration type is like the Boolean type but with the possible values 
defined by the programmer. They are declared with the enum keyword, and
the values are defined with the case keyword between braces. Variables declared of this type may only have the values allowed by the
type (one, two, or three). To assign a value, we must use the name of the
Listing 3-127: Defining an enumeration type enumeration and dot notation. The mynumber variable declared in Listing 3-

129 is of type Number and has the value one.
enum Number {
case one Once the type of the variable was already defined, only the dot and the
case two value are necessary to modify its value.
case three
}

Listing 3-130: Assigning a new value to a variable of type Number

enum Number {
This example defines an enumeration call Number with three possible case one, two, three
values: one, two, and three. We can assign any names we want for the }
var mynumber = Number.one
enumeration and its values. The values may also be declared just in one mynumber = .two
case statement separated by comma. 

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 value provided is of the same type as the variable’s
case one, two, three
} type, so it is not 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 to each case.
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 of this type and the value two is assigned to it. rawValue, which lets us read the raw value of each case.
Next, a switch statement 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"

Additionally, enumerations include an initializer to create an instance from


a raw value. Instead of declaring the variable using the value's name (one),
we can use the initializer and the raw value. The initializer includes the return "We have to study more"
case .three:
rawValue argument to specify the value used to create the instance. return "This is just the beginning"
}
}
Listing 3-134: Creating an enumeration from a raw value
}
 var mynumber = Number.two
enum Number: String { print(mynumber.printMessage()) // "We have to study more"
case one = "Number One" 
case two = "Number Two"
case three = "Number Three" When we need to check the current value of the instance from inside a
}
var mynumber = Number(rawValue: "Number Two") method, we must use the self keyword. This keyword refers to the instance
where the method is being executed (in our case, mynumber), and this is
if mynumber == .two {
print("Correct Value") // "Correct Value"
how we can check for the instance’s current value and return the right
} message. (We will learn more about the self keyword later.)

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 current value of the instance.

Listing 3-135: Adding methods to an enumeration



enum Number: Int {
case one
case two
case three

func printMessage() -> String {


switch self {
case .one:
return "We are the best"
case .two:

Associated Values parenthesis, as we did with tuples before (see Listing 2-37). If we need to
test a single case, we can use an if or a guard statement and assign the value

to the case we want to test.

Enumerations include the possibility to associate values to a case. These


Listing 3-137: Reading associated values from an if statement
are values we can attach to a case when variables of that type are

initialized. For instance, in the following example we create an
enum MyCharacters {
enumeration that can store information about a character, but it provides case number(Int, String)
the possibility to differentiate between letters and numbers. case letter(Character, String)
}
var character = MyCharacters.number(1, "Number One")
Listing 3-136: Associating values
 if case .number(let number, let text) = character {
print("Number: \(number)") // "Number: 1"
enum MyCharacters { print("Text: \(text)") // "Text: Number One"
case number(Int, String) }
case letter(Character, String) 
}
var character = MyCharacters.number(1, "Number One")
The syntax includes the case keyword and the necessary constants to
switch character { receive the values. The statement is saying something like "Assign the
case .number(let value, let description):
print("\(description) - \(value)") // "Number One - 1"
value to this case, if not possible, return false". In our example, if the
case .letter(let letter, let description): character variable doesn't contain a MyCharacters enumeration with the value
print("\(description) - \(letter)")
number, the statement returns false and nothing is done, otherwise, the
}
 associated values in the character variable are assigned to the constants and
printed on the console.
This example defines an enumeration called MyCharacters that includes two
cases. The first case is called number and it takes two associated values: an
integer and a string. The second case is called letter and it also takes two
associated values: a character and a string. When we create a value of this
type, we must select the case value, as always, but we must also specify
the associated values. If the value of the enumeration is number, we must
provide an integer and a string, and if the value is letter, we must provide a
character and a string. In this example, we create an instance with the
value number and the associated values 1 and "Number One".
In each case of the switch statement, we test whether the value is number or
letter and extract their associated values with constants between
Collection Difference we may have data stored in an array that represents the information saved
by the user on a file, and another array with a copy of that data that we are

using to show the information on the screen. If the data from the file
Associated values are commonly implemented by libraries and frameworks changes, we must update the data that is shown to the user. If that data is
to share data with our code. For instance, the Array structure defined in the stored in ordered collections, such as arrays, we can synchronize them with
Swift Standard Library includes the difference() method. We have introduced the difference() method.
this method before. Its purpose is to compare two arrays and return a
structure of type CollectionDifference that contains a collection of values that Listing 3-138: Synchronizing arrays
represent the difference between the arrays. These values are of type 
Change, an enumeration defined by the CollectionDifference structure that var list1 = [1, 2, 3, 4, 5]
var list2 = [2, 4, 8, 16, 32]
includes the following values and their associated values to represent
single changes. let diff = list1.difference(from: list2)
for change in diff {
switch change {
insert(offset: Int, element: Element, associatedWith: Int?)— case .insert(let offset, let element, _):
This enumeration value represents the insertion of an element. It list2.insert(element, at: offset)
case .remove(let offset, _, _):
includes three associated values. The offset value indicates the index list2.remove(at: offset)
in which the element is inserted, the element value is the element, }
}
and the associatedWith value is the index of the element associated print(list2) // "[1, 2, 3, 4, 5]"

with this change.
remove(offset: Int, element: Element, associatedWith: Int?)— The code in Listing 3-138 defines two arrays: list1 and list2. In this example,
This enumeration value represents the removal of an element. It we are assuming that list2 is the array that has to be synchronized with the
includes three associated values. The offset value indicates the index changes in list1, so we determine the current differences by calling the
from which the element was removed, the element value is the difference() method on list1. This gives us a collection of Change values that tell
removed element, and the associatedWith value is the index of the us which elements of the list2 array we must remove or add. Next, we
element associated with this change. create a for in loop to iterate over the collection. Because these values are
enumeration values with associated values, we must use a switch statement
As we will see later, the code that comprises an application is usually to process them. In the case the value is insert(), we insert a new element in
divided into separate units that take care of very specific tasks, like storing
the list2 array with the insert() method (see Listing 3-81), but if the value is
the user's data or showing that data on the screen. This means that remove(), we remove the element with the remove() method.
sometimes we may have two sets of data that represent the same
information and therefore we must keep them synchronized. For instance,

At the end of this process, the array stored in the list2 variable will be the 3.5 Objects
same as the one stored in the list1 variable. Of course, we could have

simplified this process by assigning list1 to list2 but knowing exactly which
elements change in one array with respect to the other is useful when we Objects are data types that encapsulate data and functionality in the form
need to perform tasks after a change is applied. of properties and methods, but unlike the structures and enumerations
If we don't need to process the changes one by one but still want to modify introduced before they are stored by reference, which means that more
the array with the values returned by the difference() method, we can call the than one variable can refer to the same object in memory.
applying() method offered by the Array structure to perform all the changes
at once.

Listing 3-139: Applying all the changes at once



var list1 = [1, 2, 3, 4, 5]
var list2 = [2, 4, 8, 16, 32]

let diff = list1.difference(from: list2)


if let newlist = list2.applying(diff) {
list2 = newlist
}
print(list2) // "[1, 2, 3, 4, 5]"

The applying() method returns the array with all the changes applied, or
nilif it can't apply the difference to the array, so we have to check this value
and then assign the result back to list2. The result is the same as before, the
array in list2 contains the same values as list1, but with the difference that all
the changes were performed at once.
Definition of Objects In Listing 3-141, the Employee() initializer creates a new instance of the class
Employee. Since in this case the words instance and object are synonyms, we

can say that we have created a new object called employee1 containing two
properties, name and age.
Like structures and enumerations, objects are defined first and then
Of course, we can also modify the values of the properties of an object
instances are created from their definition. The definitions of objects are
from its methods, but unlike structures, the object's methods can modify
called classes, and what we called objects are the instances created from
the properties of their own object without adding anything to the
those classes. Classes are declared the same way as structures or
definition (they do not need to be declared as mutating).
enumerations, but instead of the struct or enum keywords we must use the
class keyword.
Listing 3-142: Modifying properties from the object’s methods

Listing 3-140: Defining a class class Employee {
 var name = "Undefined"
var age = 0
class Employee {
var name = "Undefined"
func changename(newname: String, newage: Int) {
var age = 0
name = newname
}
age = newage

}
}
This example defines a simple class called Employee with two properties: let employee1 = Employee()
employee1.changename(newname: "Martin", newage: 32)
name and age. As always, this does not create anything, it is just defining a print("Name: \(employee1.name)") // "Name: Martin"
new custom data type. To store data in memory in this format, we must 

declare a constant or a variable of this type and assign to it an instance of


In Listing 3-142, the changename() method is added to the Employee class to
the class created with an initializer.
modify the values of its properties. After the instance is created, we call
Listing 3-141: Creating an object from a class this method to assign the values "Martin" and 32 to the name and age
 properties, respectively.
class Employee {
var name = "Undefined"
var age = 0
}
let employee1 = Employee()
employee1.name = "John"
employee1.age = 32

Type Properties and Methods


We have studied type properties and methods before with structures.


These are properties and methods that are accessible from the data type
and not from the instances created from that type. They work in classes
the same way as in structures, but instead of the static keyword we must
use the class keyword to define them.

Listing 3-143: Declaring a type method for a class



class Employee {
var name = "Undefined"
var age = 0

class func description() {


print("This class stores the name and age of an employee")
}
}
Employee.description()

This example defines an Employee class with two properties: name and age.
The type method declared next is just describing the purpose of the class.
Every time the description() method is executed on the class, a description is
printed on the console. Again, we don't have to create an instance because
the method is executed on the class itself.

IMPORTANT: Classes can also use the static keyword to define type
properties and methods. The difference between the static and class
keywords is that properties and methods defined with the static
keyword are immutable and those defined with the class keyword can
be modified by subclasses. (We will learn about subclasses and
inheritance later in this chapter.)
Reference Types nameproperty is reflected in the other because both constants point to the
same object in memory (they refer to 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, the instance is copied, and
we end up with two instances of the same structure in memory.

Figure 3-1: Value types stored in memory

Constants or variables that were assigned an object do not store the


object; they store the value of the memory address where the object is
located. When a constant or a variable containing this address is assigned
to another constant or variable, only the address is copied, and therefore
the object is not duplicated. This is the most important characteristic of
 objects, and what makes them suitable for situations in which data in
memory must be accessed and shared by different parts of the code.
Figure 3-1 shows how two different copies of the Employee structure, one
referenced by the employee1 variable and the other referenced by the IMPORTANT: Because constants and variables store a reference to
employee2 variable, are stored in memory. Any modification to the values of an object (a memory address), two or more variables in your code
one of the instances will not affect the other, because they occupy may reference the same object. If you need to know whether this is
different spaces in memory. the case, you can compare the variables with the operators ===
Objects, on the other hand, are passed by reference. This means that when (identical to) and !== (not identical to) provided by Swift. If what you
we assign an object to a constant or a variable, a reference to the object is need is to know whether two objects contain different information,
assigned to it, not a copy of the object. In the following example, the you can use the basic operators == and !=, but you can only do this
object in employee2 is the same as the object in employee1. Any change in the when the objects conform to the Equatable protocol; a protocol that

determines how the objects will be compared. We will study Self


protocols and the Equatable protocol later in this chapter.

Because the same object may be referenced by multiple constants or


variables, every language that works with objects offers a way for the
object to reference itself. In Swift this is done automatically, but there are
situations in which this reference must be declared explicitly. For this
purpose, Swift defines a special keyword called self. We have introduced
this keyword earlier in this chapter to be able to read the current value of
an enumeration from inside the instance (see Listing 3-135). In structures
and objects, the self keyword works the same way; it references the
instance the code belongs to.
The most common situation in which the use of this keyword is required is
when we need to declare the names of the parameters of a method equal
to the names of the object’s properties. If the names are the same, the
system doesn't know whether we are referring to the property or the
parameter. The self keyword clarifies the situation.

Listing 3-144: Referring to the object with self



class Employee {
var name = "Undefined"

func changename(name: String) {


self.name = name
}
}
let employee1 = Employee()
employee1.changename(name: "Martin")

print("Name: \(employee1.name)") // "Name: Martin"


The self keyword in the changename() method of Listing 3-144 represents the
object created from the Employee class and helps the system understand
what we are referring to 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 since we do not know
the name of the variable that is going to store the instance when the class Because objects are stored by reference, they can be referenced by several
is defined, we must use self instead. variables at the same time. If a variable is erased, the object it references
Another useful application of the self keyword is to reference the data type cannot be erased from memory because another variable could still be
itself. The value generated by reading the self keyword on a data type is using it. This creates a situation in which the device's memory is filled with
called metatype. A metatype refers to the type itself, not an instance of it. objects that are no longer necessary. The solution provided by Apple is an
For example, the value Int.self refers to the definition of the Int data type, automatic system that counts the number of variables referencing an
not an integer number created from that type, as shown in the following object and only removes the object from memory when all the references
example. are erased (all the variables were erased, set to nil, or they were assigned a
reference to another object). The system is called ARC (Automatic
Listing 3-145: Referring to the data type with self Reference Counting). ARC automatically erases the objects when there is

no longer a constant or a variable containing a reference to that space in
let reference = Int.self
let newnumber = reference.init(20) 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-145 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 declare the
init() method implicitly to create an instance. Metatypes are widely used to
Listing 3-146: Referencing one object from another
pass 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 Figure 3-4: Objects preserved in memory


department?.area = "Mail"
department?.person = employee

This example defines two classes: Employee and Department. Both classes
contain a property that references an object of the other class (location and
person). After the definition, objects of each class are created and stored in

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

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

The Employee class declared in Listing 3-148 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 showing its name and age. class Employee {
var name = "Undefined"
But for the sake of argument, let’s say that some of the employees require var age = 0
a badge that also displays the department they work in. One option is to
func createbadge() -> String {
define another class with the same properties and methods and add what return "Employee \(name) \(age)"
we need, but this produces redundant code, and it is difficult to do when }
}
the class was taken from a library (they are usually too complex to modify class OfficeEmployee: Employee {
or duplicate). The solution is to create a new class that inherits the var department = "Undefined"
}
characteristics of the basic class and adds its own properties and methods let employee = OfficeEmployee()
to satisfy the new requirements. To indicate that a class inherits from employee.name = "George"
employee.age = 25
another class, we must write the name of the basic class after the name of employee.department = "Mail"
the new class separated by a colon.
var badge = employee.createbadge()
print("Badge: \(badge)") // "Badge: Employee George 25"
Listing 3-149: 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" another class like OfficeEmployee is called subclass. In these terms, we can say
var age = 0
that the OfficeEmployee class is a subclass that inherits the properties and
func createbadge() -> String { methods of its superclass Employee. 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 {
object’s class and, if it is not there, it keeps looking in the superclasses up
var department = "Undefined"
} 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-149 only has one example, considering the code in Listing 3-150, 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.

Listing 3-150: Creating objects from a subclass Because of this hierarchical chain, sometimes a method does not have
 access to all the properties available for the object. For example, the
method called on the employee object created in Listing 3-150
createbadge() OfficeEmployee(the old method from the superclass is ignored), and the
have access to the properties declared on the Employee class but not those badge generated includes the values of the three properties.
declared in the OfficeEmployee class. If we want the method to also print the Using inheritance, we have created a new class without modifying
value of the department property, we must implement it again in the previous classes or duplicating any code. The Employee class can create
OfficeEmployee class with the appropriate modifications. This is called objects to store the name and age of an employee and generate a badge
overriding. To override a method of a superclass, we prefix it with the with this information, and the OfficeEmployee class can create objects to
override keyword. store the name, age, and the department of the employee and generate a
more complete badge with the values of all these properties.
Listing 3-151: Overriding an inherited method When we call the createbadge() method on the employee object created
 from the OfficeEmployee class in Listing 3-151, the method executed is the
class Employee { one defined in the OfficeEmployee class. If we want to execute the method on
var name = "Undefined"
var age = 0 the superclass instead, we must use a special keyword called super. The
super keyword is like the self keyword, but instead of referencing the object,
func createbadge() -> String {
return "Employee \(name) \(age)" super represents the superclass. It is often used when we have overridden a
} method, but we still need to execute the method on the superclass.
}
class OfficeEmployee: Employee {
var department = "Undefined" Listing 3-152: Calling a method on the superclass

override func createbadge() -> String {
return "Employee \(department) \(name) \(age)" class Employee {
} var name = "Undefined"
} var age = 0
let employee = OfficeEmployee()
employee.name = "George" func createbadge() -> String {
employee.age = 25 return "Employee \(name) \(age)"
employee.department = "Mail" }
}
var badge = employee.createbadge() class OfficeEmployee: Employee {
print("Badge: \(badge)") // "Badge: Employee Mail George 25" var department = "Undefined"

override func createbadge() -> String {
let oldbadge = super.createbadge()
The new OfficeEmployee subclass of Listing 3-151 overrides the return "\(oldbadge) \(department)"
method of its superclass to generate a string that includes the
createbadge() }
}
value of the department property. Now, when the method is executed from let employee = OfficeEmployee()
an object of this class, the method called is the one declared in employee.name = "George"
employee.age = 25
employee.department = "Mail"

Type Casting
var badge = employee.createbadge()
print("Badge: \(badge)") // "Badge: Employee George 25 Mail" 

Inheritance not only transfers functionality from one class to another but
This is the same as the previous example, but now, when the
also connects the classes together. The superclasses and their subclasses
createbadge()method of an object created from the OfficeEmployee class is
are linked together in a hierarchical chain. Because of this, whenever we
called, the method calls the createbadge() method of the superclass first and declare a variable of the type of the superclass, objects of the subclasses
assigns the result to the oldbadge constant. The value of this constant is later can be assigned to that variable too. This is a very important feature that
added to the value of the department property to generate the final string. allows us to do things like creating arrays of objects that are of different
classes but belong to the same hierarchy.

Listing 3-153: Creating an array of objects from different subclasses



class Employee {
var name = "Undefined"
var age = 0
}
class OfficeEmployee: Employee {
var deskNumber = 0
}
class WarehouseEmployee: Employee {
var area = "Undefined"
}
var list: [Employee] = [OfficeEmployee(), WarehouseEmployee(), OfficeEmployee()]

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 only contain the name, age, and deskNumber properties to
represent employees working at the office and objects that only 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 }
print("We have \(countOffice) employees working at the office") //2
represent employees of the same company, so sooner or later we will have print("We have \(countWarehouse) employees working at the warehouse") //1
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-154, we create the list array again with objects from the
store objects of the subclasses in it, as we did in Listing 3-153 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 elements of the array
have found. The if statement inside the loop uses the is operator to check if
are all considered to be of type Employee, so we can only access the
the current object stored in the obj constant is of type OfficeEmployee or
properties defined in the Employee class. Also, there is no way to know what
WarehouseEmployee and increments the counter respectively (countOffice or
type of object each element is. We could have an OfficeEmployee object at
countWarehouse).
index 0 and later replace it by a WarehouseEmployee object. The indexes do
Counting objects is not really what these operators are all about. The
not provide any information to identify the objects. Swift solve these
idea is to figure out the type with the is operator and then convert the
problems with the is and as 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 indicating whether the value is of
conversions are not always guaranteed, and that is why this operator
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-155: Casting an object

Listing 3-154: 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 Listing 3-157: Casting an object on the fly
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 let myarea = (list[1] as! WarehouseEmployee).area
print("The area of employee 1 is \(myarea)") // "Undefined"
exclamation mark). In the code of Listing 3-155, we only use this operator 
after we have already checked with the is operator that the object is of the
right class. Once the object is casted (converted) into its original data type, In this example, we do not assign the object to any variable; we just cast
we can access its properties and methods. In this example, the objects the element of the list array at index 1 as a WarehouseEmployee object inside
returned by the as! operator are stored in the temp constant and then new the parentheses and then access its area property. The value of this
values are assigned to the deskNumber and area properties. property is stored in the myarea constant and then printed on the console.
Checking for the type before casting is redundant. To simplify the Remember that conversions performed with the as! operator are only
code, we can use the as? operator. Instead of forcing the conversion and possible when we are sure it is going to be successful.
crashing the app, this version of the as operator tries to perform the
conversion and returns an optional with the result of the operation. IMPORTANT: The as! operator is applied when the conversion is
guaranteed to be successful, and the as? operator is used when we
Listing 3-156: Casting an object with the as? operator are not sure about the result. But we can also use the basic as
 operator when the Swift compiler can verify that the conversion will
for obj in list { be successful, as when we are casting some primitive data types
if let temp = obj as? OfficeEmployee { (e.g., String values into NSString objects).
temp.deskNumber = 100
} else if let temp = obj as? WarehouseEmployee {
temp.area = "New Area" The as operator works on objects that belong to the same class
}
}
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, AnyObject, and
In this example, we use optional binding to cast the object and assign AnyClass. Taking advantage of these generic types, we can create collections
the result to the temp constant. First, we try to cast obj as an OfficeEmployee with values that are not associated with each 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-158: Working with objects of AnyObject type
the WarehouseEmployee class to 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"
}
var list: [AnyObject] = [Employee(), Department(), Department()] Initialization
for obj in list { 
if let temp = obj as? Employee {
temp.name = ""
} else if let temp = obj as? Department { We have been initializing the properties during definition in every class
temp.area = ""
} declared so far. This is because classes do not provide memberwise
}
initializers as structures do. The properties of a class have to be initialized

explicitly in the definition or during instantiation by the init() method. This is
The list array declared in Listing 3-158 is of type AnyObject and therefore the same method previously introduced for structures and we can use it in
it can contain objects of any data type. To populate the array, we created our classes as well.
two simple and independent classes: Employee and Department. A few objects
are created from these classes and included in the array. The objects are Listing 3-159: Declaring a Designated Initializer
later casted by the as? operator inside a for in loop and their corresponding 

properties are modified following the same procedure used in previous class Employee {
var name: String
examples. var age: Int

init(name: String, age: Int) {


self.name = name
self.age = age
}
}
let employee1 = Employee(name: "George", age: 28)

The init() method declared for the Employee class in Listing 3-159 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 default values for some or all its properties.
It is declared as an init() method but preceded with the convenience keyword.

A Convenience Initializer must call the Designated initializer of the same var name: String
var age: Int
class with the corresponding values.
init(name: String, age: Int) {
self.name = name
Listing 3-160: Declaring a Convenience Initializer self.age = age
 }
}
class Employee {
class OfficeEmployee: Employee {
var name: String
var department: String = "Undefined"
var age: Int }
let employee1 = OfficeEmployee(name: "George", age: 29)
init(name: String, age: Int) {

self.name = name
self.age = age
} The code in Listing 3-161 defines the subclass OfficeEmployee that inherits
convenience init() {
self.init(name: "Undefined", age: 0) from the Employee class. The OfficeEmployee class does not provide any
} initializer, so the only initializer available is the one provided by its
}
let employee1 = Employee() superclass. This initializer only initializes the properties name and age. The
 department property of OfficeEmployee is explicitly initialized with the value
"Undefined". To provide an initializer that also includes this property, we
When we create an instance of Employee, the system detects the number
must declare a new Designated Initializer in the OfficeEmployee class.
and type of arguments provided and executes the corresponding initializer.
For example, if we provide the values for the name and the age parameters, Listing 3-162: Declaring a Designated Initializer for the subclass
the system executes the Designated Initializer because this is the initializer 
that contains the necessary parameters to receive those values, but if the class Employee {
initialization does not include any argument, the Convenience Initializer is var name: String
var age: Int
executed instead and then the Designated Initializer is called with values by
default ("Undefined" and 0). init(name: String, age: Int) {
self.name = name
Unlike structures, classes can inherit properties and methods from other self.age = age
}
classes, and this includes the init() method. When a subclass does not
}
provide its own Designated Initializer, the initializer of its superclass is used class OfficeEmployee: Employee {
var department: String
instead.
init(name: String, age: Int, department: String) {
Listing 3-161: Inheriting the Designated Initializer self.department = department

 super.init(name: name, age: age)


class Employee { }
} Deinitialization
let employee1 = OfficeEmployee(name: "John", age: 24, department: "Mail")
 
The Designated Initializer of a subclass must initialize the properties of its
There is a counterpart of the initialization process called Deinitialization.
own class first and then call the initializer of its superclass. This is done by
Despite its name, this process is not directly related to the initialization
calling the init() method on super. The super keyword refers to the superclass,
process but rather to the ARC system. ARC, as we studied previously in this
so when the system executes the super.init() statement in the code of Listing
chapter, is an automatic system adopted by Swift to manage memory.
3-162, the init() method of the superclass is executed and the name and age
Letting the system manage the memory and take care of removing the
properties of this class are initialized.
objects our program does no longer need presents a huge advantage, but it
also means that we do not always know when an object is going to be
IMPORTANT: There are different ways to combine Designated and
removed. There are times when an object is using resources that must be
Convenience initializers. The possibility of classes to inherit from
other classes in an unlimited chain can turn initialization into a very closed or information that needs to be stored. Whatever the task, Swift
complex process. This book does not explore all the possibilities offers the deinit method to execute any last-minute instructions we need
provided by Swift for initialization. For more information, visit our before the object is erased from memory.
website and follow the links for this chapter.
Listing 3-163: Declaring a deinitializer

class Item {
var quantity = 0.0
var name = "Not defined"
var price = 0.0

deinit {
print("This instance was erased")
}
}
var purchase: Item? = Item()
purchase = nil

This example defines a simple class with a deinitializer. The object is


created and assigned to an optional variable. Right after that, the nil value
is assigned to the same variable to erase the reference and test the deinit
method, which prints a message on the console.

Access Control and Modifiers The Employee class in Listing 3-164 defines two properties, name and age, but
this time a closure is assigned to the name property to get the employee's

name from a server (we will see how to retrieve information from the Web
Swift defines keywords (also called modifiers) that can be applied to in Chapter 19). Because we declared this property as lazy, the closure will
entities (classes, structures, properties, methods, etc.) to confer them only be executed when we try to read the property’s value. If we execute
special attributes. We have already seen the mutating and override keyword, the example as it is, we get nothing in return, but if we read the name
but there are more available, like the following. property with a statement at the end, we will see the text "Loading..."
printed on the console.
lazy—This keyword defines a property whose initial value is not The Swift language also includes keywords in charge of defining the level of
assigned until the property is used for the first time. access for each entity in our code. Access control in Swift is based on
modules and source files, but it also applies to single properties and
final—This keyword is used on a class when we don't want to allow
methods. Source files are the files we create for our application, the
the code to create subclasses of it. It must be declared before the class
properties and methods are the ones we have created for our structures
keyword. and classes in previous examples, and modules are units of code that are
related. For instance, a single application and each of the frameworks
The lazy keyword is frequently used when the property’s value may take included in it are considered modules (we will introduce frameworks in
time to be determined and we do not want the initialization of the Chapter 4). Considering this classification, Swift defines five keywords to
structure or the class to be delayed. For example, we may have a property determine accessibility.
that stores a name retrieved from a server, which is a resource intensive
task that we should perform only when the value is required. open—This keyword determines that an entity is accessible from the
module it belongs to and other modules.
Listing 3-164: Defining lazy properties

public—This keyword determines that an entity is accessible from
class Employee { the module it belongs to and other modules. The difference between
lazy var name: String = { public and open is that we can't create subclasses of public classes
// Loading name from a server
print("Loading...") outside the module in which they were defined. This also applies to
methods and properties (e.g., public methods can't be overridden
return "Undefined"
}() outside the module in which they were declared).
var age = 0
} internal—This keyword determines that an entity is accessible only
let employee = Employee() inside the module in which it was created. This is the default access

mode for applications. By default, every entity defined in our
application is only accessible from inside the application.
private—This keyword determines that an entity is accessible only
from the context in which it was created (e.g., a private property in a The code in Listing 3-165 defines the name and age properties of our Employee
class will only be accessible from methods of the same class). 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
fileprivate—This keyword determines that an entity is accessible
in the class. If we try to read their values from outside the object using dot
only from the file in which it was declared (e.g., a fileprivate property in
notation, Xcode will return an error (employee.name).
a class will only be accessible by other entities defined inside the file If what we want is to be able to read the property from outside the object
in which it was declared). but not allow assigning new values to it, we can declare it with a public
getter but a private setter.
As we will see later, most of these keywords apply to frameworks and are
rarely used in single applications. By default, the properties and methods Listing 3-166: Declaring a public getter and private setter
we include in our classes and structures are declared internal, which means 

that our classes and structures are only available from inside our class Employee {
private var name = "Undefined"
application (module). Unless we are creating our own frameworks, this is public private(set) var age = 0
all we need for our applications, and is the reason why we didn't have to
func setAge(newAge: Int) {
specify any keyword when we defined our structures and classes before. age = newAge
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 let employee = Employee()
employee.setAge(newAge: 25)
or avoid modifying values by mistake, we can declare some of them as
print(employee.age)
private, as in the following example. 

Listing 3-165: Declaring private properties The age property in the Employee class of Listing 3-166 was declared as
 public, so everyone can read its value, but with a private setter (private(set)),
class Employee { so only the methods inside the class can modify it. To change the value, we
private var name = "Undefined" defined the setAge() method. The code creates an instance of the class and
private var age = 0
calls the method, but this time we can read the value of the age property
func showValues() { and print it on the console because it was declared with a public getter.
print("Name: \(name)")
print("Age: \(age)")
}
}
let employee = Employee()
employee.showValues()

3.6 Protocols common are defined in the protocol and then implemented by the
structures’ definitions. This lets us associate different structures together

through a common pattern. The code implemented by each structure is
unique, but they follow a blueprint set by the protocol. If we know that a
The main characteristics of classes, and therefore objects, are the capacity
structure uses a protocol, we can always be sure that besides its own
to encapsulate data and functionality and the possibility to share and
definitions, it will also include the properties and methods defined by the
improve code through inheritance. This introduced an advantage over
protocol. In addition, protocols can be extended to provide their own
previous paradigms and turned Object-Oriented Programming into the
implementations of the properties and methods we want the structures to
industry standard for a while. But that changed with the introduction of
have in common, allowing the paradigm to completely replace classes and
protocols in Swift. Protocols define properties and methods that structures
objects.
can have in common. This means that Swift’s structures not only can
encapsulate data and functionality, just like objects, but by conforming to
IMPORTANT: The Swift paradigm is built from the combination of
protocols they can also share code. Figure 3-5 illustrates the differences
structures and protocols, but protocols may also be adopted by
between these two paradigms.
enumerations and classes. For instance, Objective-C and the
frameworks programmed in this language use protocols to offer a
Figure 3-5: Object-Oriented Programming versus Protocol-Oriented
programming pattern called Delegation. We will study how classes
Programming conform to protocols and how to implement delegation later.

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
Definition of Protocols this protocol, and along with the protocol’s property and method it also
implements its own property called age. Although this property was not

defined in the protocol, we can read it inside the printdescription() method
Protocols are defined with the protocol keyword followed by the name and and print its value.
the list of properties and methods between braces. No values or The advantage of this practice is evident when structures of different types
statements are assigned or declared inside a protocol, only the names and conform to the same protocol, as shown in the following example.
the corresponding types. Because of this, methods are defined as always,
Listing 3-168: Defining multiple structures that conform to the same
but they omit the braces and the statements, and properties must include
protocol
the get and set keywords between braces to indicate whether they are read-

only properties, or we can read and assign values to them (see Listing 3-43
protocol Printer {
for an example of getters and setters). To indicate that the structure var name: String { get set }
conforms to the protocol, we must include the protocol’s name after the func printdescription()
}
structure’s name separated by a colon, as shown in the following example. struct Employees: Printer {
var name: String
var age: Int
Listing 3-167: Defining protocols
 func printdescription() {
print("Description: \(name) \(age)")
protocol Printer {
}
var name: String { get set }
}
func printdescription()
struct Offices: Printer {
}
var name: String
struct Employees: Printer {
var employees: Int
var name: String
var age: Int
func printdescription() {
print("Description: \(name) \(employees)") // "Description: Mail 2"
func printdescription() {
}
print("Description: \(name) \(age)") // "Description: John 32"
}
}
let employee1 = Employees(name: "John", age: 32)
}
let office1 = Offices(name: "Mail", employees: 2)
let employee1 = Employees(name: "John", age: 32)
office1.printdescription()
employee1.printdescription()

A protocol tells the structure what properties and methods are required, Although the structures created in Listing 3-168 from the Employees and
Offices definitions are different (they have different properties), they both
but the structure must implement them. In the example of Listing 3-167,
we define a protocol called Printer that includes the name property and the conform to the Printer protocol and provide their own implementation of
printdescription() method. The Employees structure defined next conforms to
the printdescription() method. The common functionality defined by the

protocol ensures that no matter what type of structure we are working var list: [Printer] = [employee1, office1]
for element in list {
with, it will always have an implementation of printdescription(). if let employee = element as? Employees {
Protocols are also data types. We can treat a structure as if it is of the data print(employee.age) // "32"
}
type of the protocol it conforms to. This allows us to associate structures element.printdescription()
by their common functionality. }

Listing 3-169: Using protocols as data types



Because protocols are data types, we can use them to define variables, or
let employee1 = Employees(name: "John", age: 32)
return them from functions. The following example declares a function
let office1 = Offices(name: "Mail", employees: 2) that returns a value of type Printer.
var list: [Printer] = [employee1, office1]
for element in list { Listing 3-171: Returning values of a protocol data type
element.printdescription() 
}
 func getFile(type: Int) -> Printer {
var data: Printer!
if type == 1 {
Listing 3-169 uses the same protocol and structures defined in the previous data = Employees(name: "John", age: 32)
example, but this time it stores the instances in an array. The type of the } else if type == 2 {
data = Offices(name: "Mail", employees: 2)
array was defined as Printer, which means the array may contain structures }
return data
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 let file = getFile(type: 1)
file.printdescription() // "Description: John 32"
always have an implementation of the name property and the printdescription() 
method.
When we process a structure or an object as a protocol type, we can only The getFile() function in Listing 3-171 creates an instance of a structure
access the properties and methods defined by the protocol. If we need to depending on the value received. If the type parameter is equal to 1, it
access the instance's own properties and methods, we must cast it using returns an instance of Employees, but if the value is equal to 2, it returns an
the as operator as we did with classes before. The following example prints instance of Offices. But because the value returned by the function is of type
the value of the age property if the element of the array is of type Employees. Printer we know it will always include the printdescription() method.

Listing 3-170: Accessing the instance's own properties



let employee1 = Employees(name: "John", age: 32)
let office1 = Offices(name: "Mail", employees: 2)
Generic Protocols Swift Protocols
 

Protocols can also define generic properties and methods, but they work The Swift language makes use of protocols extensively. Almost every API
slightly different than the generic types studied before. When we want to includes protocols that define common features and behavior for their
define a protocol with a generic property or method, we first must define data types, including enumerations, structures, and classes. But there are
the name of the generic type with the associatedtype keyword. also important protocols defined in the Swift Standard Library that we can
use to improve our custom data types. The following are some of the
protocols available.
Listing 3-172: Defining generic protocols

Equatable—This protocol defines a data type which values can be
protocol Printer {
associatedtype protype compared with other values of the same type using the operators ==
var name: protype { get set } and !=.
}
struct Employees: Printer { Comparable—This protocol defines a data type which values can be
var name: String
} compared with other values of the same type using the operators >, <,
let employee = Employees(name: "John") >=, and <=.
print(employee.name) // "John"
 Numeric—This protocol defines a data type that only works with
values that can participate in arithmetic operations.
The code in Listing 3-172 defines a generic protocol called Printer and a
Hashable—This protocol defines a data type that provides the hash
structure that conforms to that protocol called Employees. The protocol
value (unique identifier) required for the value to be included in
defines a generic type with the name protype and then a property of that
collections, such as sets and dictionaries.
type. The property's real data type is defined by the structure or the class
that conforms to the protocol. In this case, the Employees structure defines CaseIterable—This protocol defines a data type, usually an
the name property to be of type String, and that's the type of values we can enumeration without associated values, that includes a property
use in the instances of this structure, but we could have declared the called allCases that contains the collection of all the cases included in
property as an integer or any other data type we needed. the data type.

These protocols are responsible of elemental processes performed by


the system and the Swift language. For example, when we compare two
values with the == or != operators, the system checks whether the values
conform to the Equatable protocol and then calls a type method in the

values' data types to compare them and solve the condition (true or false, the value 32 and therefore the value "Equal" is assigned to the message
depending on whether the values are equal or not). Swift primitive data constant when we compare the objects with the ternary operator.
types conform to the Equatable protocol and implement its methods, but we If what we want is to compare each of the properties in the structure,
can also implement them in our own data types to compare their values. then we can omit the method. When we conform to the Equatable protocol,
For this purpose, we must declare that the data type conforms to the the compiler automatically generates the method for us to compare all the
protocol and implement the methods required by it. The Equatable protocol values of the structure (in this case, name and age).
requires only one method called == to check for equality (the system infers
that if two values are not equal, they are different, and therefore the Listing 3-174: Letting the compiler create the protocol methods for us
method for the != operator is optional). This method must have a name 
equal to the operator (==), receive the two values to compare, and return a struct Employees: Equatable {
var name: String
Boolean value to communicate the result of the comparison. For instance, var age: Int
we can make our Employees structure conform to the Equatable protocol and }
let employee1 = Employees(name: "John", age: 32)
implement a method with the name == to be able to compare two different let employee2 = Employees(name: "George", age: 32)
instances of the same structure.
let message = employee1 == employee2 ? "Equal" : "Different"
print(message) // "Different"
Listing 3-173: Conforming to the Equatable protocol 

struct Employees: Equatable { Because we did not declare the == method in the example of Listing 3-
var name: String 174, the system creates the method for us and compares the values of
var age: Int
both properties. As a result, the objects are considered different (the ages
static func == (value1: Employees, value2: Employees) -> Bool { are the same, but the names are different).
return value1.age == value2.age
} Of course, we could have compared the properties directly
} (employee1.name == employee2.name) but being able to compare the objects
let employee1 = Employees(name: "John", age: 32)
let employee2 = Employees(name: "George", age: 32) instead simplifies the code and allows us to use our structures (or objects)
in APIs that require the values to be comparable. For example, when we
let message = employee1 == employee2 ? "Equal" : "Different"
print(message) // "Equal" created a generic function earlier in this chapter, we could not perform any
 operations on the values (see Listing 3-15). Because the data type we used
in those functions is generic, Swift is incapable of knowing the capabilities
In this example, we use the == method to compare the values of the of the data type and therefore Xcode returns an error if we try to perform
properties and therefore the structures are going to be equal when the
age
operations on the values, but we can easily change the situation by making
employees are the same age. In this case, both instances are created with the generic type conform to a protocol. This feature is called type
constraint because it constrains the generic type to a data type with
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 
Equatable protocol, and therefore we can compare the values inside the
function. The calculateResult() function in Listing 3-176 is a generic function and
therefore it can receive any value of any type, but because we set a type
Listing 3-175: Adding a type constraint to a generic function constraint with the Numeric protocol, the function can only receive values of
 data types that can participate in arithmetic operations.
struct Employees: Equatable { Besides comparing for equality with the Equatable protocol, we can also
var name: String
var age: Int compare magnitudes with the Comparable protocol. This protocol is like
} Equatable, but the system does not offer a default implementation of the
func compareValues<T: Equatable>(value1: T, value2: T) -> String {
let message = value1 == value2 ? "equal" : "different" type methods, we must implement them ourselves. The protocol requires
return message four methods to represent the operations >, <, >= and <=. In the following
}
let employee1 = Employees(name: "George", age: 55)
example, we compare the ages of the employees.
let employee2 = Employees(name: "Robert", age: 55)
Listing 3-177: Conforming to the Comparable protocol
let result = compareValues(value1: employee1, value2: employee2)
print("The values are \(result)") // "The values are different" 
 struct Employees: Comparable {
var name: String
var age: Int
The conformance to the protocol is specified inside the angle brackets
after the name of the generic type. The compareValues() function in Listing 3- static func > (value1: Employees, value2: Employees) -> Bool {
175 declares the T type to conform to Equatable and then compares the return value1.age > value2.age
}
values with a ternary operator and returns the result. In this case, the ages static func < (value1: Employees, value2: Employees) -> Bool {
of the employees are the same (55), but the names are different ("George" return value1.age < value2.age
}
and "Robert"), and therefore the system considers the structures to be static func >= (value1: Employees, value2: Employees) -> Bool {
different. return value1.age >= value2.age
}
Another protocol used as a type constraint is Numeric. This protocol static func <= (value1: Employees, value2: Employees) -> Bool {
determines that the data types of the values received by the function must return value1.age <= value2.age
}
support arithmetic operations. }
let employee1 = Employees(name: "George", age: 32)
let employee2 = Employees(name: "Robert", age: 55)
Listing 3-176: Using the Numeric protocol to set a type constraint
 if employee1 > employee2 {
func calculateResult<T: Numeric>(value1: T, value2: T) { print("\(employee1.name) is older")
} else {
print(value1 + value2) // 7.5

print("\(employee2.name) is older") // "Robert is older" for item in list {


} print(item.name)
 }

When we compare two instances of the Employees structure, the system


Hash values are random integers created based on the values of the
calls the corresponding type method and the method returns true or false
properties. If we just conform to the protocol, the system uses the values
according to the values of the age properties. Because in this example the of all the properties in the instance to create it (all the properties must be
value of age in the employee1 structure is not greater than the value of age in hashable), but we can specify which properties should be included by
the employee2 structure, we get the message "Robert is older". implementing the properties and methods defined by the protocol.
Another useful protocol is Hashable. Every time we include a structure
or an object in a set or use them as the index of a dictionary, the system hashValue—This property returns the instance's hash value. It is of
requires the data type to provide a hash value that can be used to uniquely type Int.
identify each item. This is a random integer that is created based on the hash(into: inout Hasher)—This method defines the properties that
values of the properties. The function of the Hashable protocol is to define
are going to be included in the hasher to create the hash value.
properties and methods to handle this value. Most of the data types
defined by Swift conform to this protocol and that is why we do not have
To calculate the hash value, the Swift Standard Library includes a
any problems when including these values in a set or as the index of
structure called Hasher. This is the structure received by the hash(into:)
dictionaries, but for custom structures and objects we must provide the
method and it contains a method called combine() to tell the hasher which
hash value ourselves. Fortunately, if the values in our data type are already
properties should be used to create the value. The following example
Hashable, we do not need a specific property to be used to create the hash
illustrates how to implement the hash(into:) method and call the combine()
value, all we need to do is to conform to the protocol and the system
method on the hasher to create a hash value from the value of the name
creates the value for us. The following example makes the Employees
property.
structure conform to the Hashable protocol, so we can include the instances
in a set. Listing 3-179: Defining our own hash value

Listing 3-178: Conforming to the Hashable protocol struct Employees: Hashable {
 var name: String
struct Employees: Hashable { var age: Int
var name: String
var age: Int func hash(into hasher: inout Hasher) {
} hasher.combine(name)
let employee1 = Employees(name: "John", age: 32) }
let employee2 = Employees(name: "Robert", age: 55) }
let employee = Employees(name: "George", age: 32)
let list: Set<Employees> = [employee1, employee2] print(employee.hashValue) // e.g., 7722685913545470055

Extensions
At the end of Listing 3-179, we print the value of the hashValue property.

Because the resulting value is always an integer calculated randomly every
time the app is executed, we won't notice any difference, but this
Protocols only define the properties and methods that the data types will
procedure may be useful when we manage sensitive information. have in common, but they do not include any implementation. However,
we can implement properties and methods that will be common to all the
IMPORTANT: The Foundation framework includes a global function data types that conform to the protocol by taking advantage of a feature of
called UUID() that creates and returns a UUID value. This is a unique the Swift language called extensions. Extensions are special declarations
value that can be used to identify instances of custom data types. that add functionality to an existent data type. We can use them with
We will study Foundation in the next chapter and implement UUID structures, enumerations, and classes, but they are particularly useful with
values in Chapter 10. protocols because this is the way protocols can provide their own
functionality. The syntax includes the extension keyword followed by the
The last protocol from our list is called CaseIterable. This is a simple name of the data type we want to extend. The following example recreates
protocol that defines a property called allCases to store a collection with all the Printer protocol introduced in previous examples and extends it with a
the cases in an enumeration. Again, the system automatically initializes this method called printdescription().
property, so all we need to do is to declare that the enumeration conforms
to the protocol. In the following example, we define an enumeration with Listing 3-181: Extending a protocol
three cases and then iterate through the collection in the allCases property 

to print the names. protocol Printer {


var name: String { get set }
}
Listing 3-180: Conforming to the CaseIterable protocol extension Printer {
func printdescription() {
 print("The name is \(name)")
enum Departments: CaseIterable { }
case mail }
case marketing struct Employees: Printer {
case managing var name: String
} var age: Int
var message = "" }
for department in Departments.allCases { struct Offices: Printer {
message += "\(department) " var name: String
} var employees: Int
print(message) // "mail marketing managing " }
 let employee = Employees(name: "John", age: 45)
let office = Offices(name: "Mail", employees: 2)

employee.printdescription() // "The name is John"

office.printdescription() // "The name is Mail" Of course, we can also extend our own data types if we consider that

appropriate. The following example extends our Employees structure to add
In this example, we define a Printer protocol with just the name property and a new method.
then extend it to include a common implementation of the printdescription()
Listing 3-183: Extending custom data types
method. Now, the Employees and Offices structures in our example share the

same implementation and produce the same result when their
struct Employees {
printdescription() methods are executed. var name: String
var age: Int
As we already mentioned, extensions are not only available for protocols
}
but also for any other data type. We can use them to extend structures, extension Employees {
func printbadge() {
enumerations, or classes. This is particularly useful when we do not have print("Name: \(name) Age: \(age)")
access to the definitions of the data types and need to add some }
}
functionality (like when they are part of a library or framework). In the let employee = Employees(name: "John", age: 50)
following example, we extend the Int structure to provide a method that employee.printbadge() // "Name: John Age: 50"

prints a description of its value.
Extensions can also be conditional. For instance, if we have a generic
Listing 3-182: Extending data types
structure, we can add an extension only for specific types of values. The

condition is determined by the where clause. The clause works like an if
extension Int {
func printdescription() { statement, so the extension is only applied if the condition is met.
print("The number is \(self)")
}
}
Listing 3-184: Defining a conditional extension
let number = 25 
number.printdescription() // "The number is 25"
struct Employees<T> {

var value: T
}
The Int data type is a structure defined in the Swift Standard Library. extension Employees where T == Int {
func doubleValue() {
We cannot modify its definition, but we can extend it to add more print("\(value) times 2 = \(value * 2)")
functionality. In this example, we add a method called printdescription() to }
}
print a message with the current value (notice the use of the self keyword let employee = Employees(value: 25)
to refer to the instance). This method is not included in the original employee.doubleValue() // "25 times 2 = 50"

definition, but it is now available in our code.
In this example, we define a generic structure called Employees with a Another useful implementation of extensions is the customization of
generic property called value and then define an extension for this structure string interpolation. We have introduced string interpolation in Chapter 2
with a method called doubleValue(), but this method will only be added to the and have been using it in almost every example to insert values into strings
instance if the data type used to create the instance is Int. At the end, we (e.g., print("My name is \(name)")). What we haven't mentioned is that these
create an instance with the value 25 and call the method, which multiplies values are managed by a structure called String.StringInterpolation (a typealias
the value by 2 and prints a string with the result. This works because we of DefaultStringInterpolation) and that by extending this structure we can
created the instance with an integer, but if we try to use another type of customize how the system processes the values. The StringInterpolation
value, Xcode will show an error. structure includes the following methods for this purpose.

appendInterpolation(Value)—This method interpolates the value


provided by the argument into the final string.
appendLiteral(String)—This method adds the string provided by
the argument to the interpolation.

To customize the interpolation, we extend the StringInterpolation


structure with an overload of the appendInterpolation() method, process the
value inside this method, and finally append the result to the interpolation
with the appendLiteral() method.

Listing 3-185: Customizing string interpolation



extension String.StringInterpolation {
mutating func appendInterpolation(celsius value: Double) {
let fahrenheit = ((value * 9)/5) + 32
appendLiteral(String(fahrenheit))
}
}
print("Temperature in Fahrenheit \(celsius: 25)")

The appendInterpolation() method can take as many parameters as we


need. In this example, we define only one parameter with the name value
and the label celsius. When we create a string with this label and a number,
the method is executed. Inside, we use the formula to turn Celsius degrees

into Fahrenheit and then add the result to the interpolation with the Delegates
appendLiteral() method to get the string "Temperature in Fahrenheit 77.0".

As we have already seen, an instance of a structure or an object can be


assigned to the property of another instance. For example, we could have
an instance of a structure called Employees with a property that contains an
instance of a structure called Offices to store information about the office
where the employee works. This opens the door to new programming
patterns where the instances adopt different roles. The most useful pattern
is called delegation. A structure or object delegates responsibility for the
execution of certain tasks to another structure or object.

Listing 3-186: Delegating tasks



struct Salary {
func showMoney(name: String, money: Double) {
print("The salary of \(name) is \(money)")
}
}
struct Employees {
var name: String
var money: Double

var delegate: Salary

func generatereport() {
delegate.showMoney(name: name, money: money)
}
}
let salary = Salary()
var employee1 = Employees(name: "John", money: 45000, delegate: salary)

employee1.generatereport() // "The salary of John is 45000.0"


The Employees structure in Listing 3-186 contains three properties. The


properties name and money store the employee’s data, while the delegate
property stores the instance of the Salary structure in charge of printing
func generatereport() {
that data. The code creates the Salary instance first and then uses this value delegate.showMoney(name: name, money: money)
to create the Employees instance. When we call the generatereport() method on }
}
the employee1 structure at the end, the method calls the showmoney() method let salary = Salary()
let employee1 = Employees(name: "John", money: 45000, delegate: salary)
on delegate, effectively delegating the task of printing the data to this
structure. employee1.generatereport() // "The salary of John is 45000.0"

This pattern presents two problems. First, the structure that is delegating
needs to know the data type of the structure that is going to become the
The delegate property of the Employees structure is now of type
delegate (in our example, the delegate property had to be declared of type
SalaryProtocol,
which means that it can store any instance of any type
Salary). Following this approach, not every structure can be a delegate, only
providing that it conforms to the SalaryProtocol protocol. As shown by this
the ones specified in the definition (only structures of type Salary can be example, the advantage of protocols is that we can use structures of
delegates of structures of type Employee). The second problem is related to different types to perform the task. It doesn't matter what type they are as
how we know which are the properties and methods that the delegate long as they conform to the delegate’s protocol and implement its
must implement. If the structure is too complex or is taken from a library, properties and methods. For example, we could create two different
we could forget to implement some methods or properties and get an structures to print the data of our last example and assign to the delegate
error when the structure tries to access them. Both problems are solved by one instance or another depending on what we want to achieve.
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 Listing 3-188: Using different delegates
shown in the following example. 
protocol SalaryProtocol {
func showMoney(name: String, money: Double)
Listing 3-187: Delegating with protocols }
 struct Salary: SalaryProtocol {
func showMoney(name: String, money: Double) {
protocol SalaryProtocol {
print("The salary of \(name) is \(money)")
func showMoney(name: String, money: Double)
}
}
}
struct Salary: SalaryProtocol {
struct BasicSalary: SalaryProtocol {
func showMoney(name: String, money: Double) {
func showMoney(name: String, money: Double) {
print("The salary of \(name) is \(money)")
if money > 40000 {
}
print("Salary is over the minimum")
}
} else {
struct Employees {
print("The salary of \(name) is \(money)")
var name: String
}
var money: Double
}
}
var delegate: SalaryProtocol

struct Employees { 3.7 Errors


var name: String
var money: Double 
var delegate: SalaryProtocol

func generatereport() { Errors are common in computer programming. Either our code or the code
delegate.showMoney(name: name, money: money)
} provided by libraries and frameworks may return errors. No matter how
} many precautions we take, we can't never guarantee success and many
let salary = Salary()
var employee1 = Employees(name: "John", money: 45000, delegate: salary)
problems may be found as our code tries to serve its purpose. For this
reason, Swift introduces a systematic process to handle errors called Error
employee1.delegate = BasicSalary() Handling.
employee1.generatereport() // "Salary is over the minimum"

The BasicSalary structure added in Listing 3-188 conforms to the


SalaryProtocol protocol and implements its showMoney() method, but unlike the
Salary structure, it produces two different results depending on the
employee’s salary. The output produced by the execution of the
generatereport() method on the Employees structure now depends on the type
of structure we previously assigned to the delegate property.
Throwing Errors lamps in stock, everything is fine, but when we sell more lamps than we
have, as in this example, there is clearly a problem.

To throw an error, we must define the types of errors available for the
method, declare the method as a throwing method adding the throws
When a method reports an error, it is said that it throws an error. Several
keyword in the declaration (between the arguments and the returning data
frameworks provided by Apple are already programmed to throw errors, as
types), detect the error, and throw it with the throw keyword.
we will see in further chapters, but we can also do it from our own
structures and classes. To throw an error, we must use the throw and throws
Listing 3-190: Throwing errors
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
enum Errors: Error {
method can throw errors. case OutOfStock
Because a method can throw multiple errors, we also must indicate the }
struct Stock {
type of error found with values of an enumeration type. This is a custom var totalLamps = 5
enumeration that conforms to the Error protocol. For instance, let’s mutating func sold(amount: Int) throws {
consider the following example. if amount > totalLamps {
throw Errors.OutOfStock
} else {
Listing 3-189: Producing an error inside a method totalLamps = totalLamps - amount
}
 }
struct Stock { }
var totalLamps = 5 var mystock = Stock()
mutating func sold(amount: Int) { 
totalLamps = totalLamps - amount
}
}
In this example, we declare an enumeration called Errors that conforms to
var mystock = Stock() 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
mystock.sold(amount: 8)
print("Lamps in stock: \(mystock.totalLamps)") // "Lamps in stock: -3" OutOfStock error every time we try to sell more lamps than we have. If the
 lamps sold are more than the number of lamps in stock, the method
throws the error, otherwise the stock is updated.
The code in Listing 3-189 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

Handling Errors check for errors. If the method returns the OutOfStock error, the statements
inside the catch block are executed. This pattern allows us to respond every

time there is an error and report it to the user or correct the situation
without having to crash the app or produce unexpected results.
Now that we have a method that can throw errors, we must handle the
errors when the method is executed. Swift includes the try keyword and
Do It Yourself: Create a new Playground file. Copy the code in
the do catch statements for this purpose. The do catch statements create two
Listing 3-191 inside the file. You should see the message "We do not
blocks of code. If the statements inside the do block return an error, the
statements in the catch block are executed. To execute a method that have enough lamps" printed on the console. Replace the number 8
throws errors, we must call the method inside the do statement with the try with the number 3. Now the message should not be printed because
keyword in front of it. there are enough lamps in stock.

Listing 3-191: Handling errors IMPORTANT: You can add as many errors as you need to the Errors
 enumeration. The errors can be checked later with multiple catch
enum Errors: Error { statements in sequence. Also, you may add all the statements you
case OutOfStock need to the do block. The statements before try are always executed,
}
struct Stock { while the statements after try are only executed if no error is found.
var totalLamps = 5
mutating func sold(amount: Int) throws {
if amount > totalLamps {
If the error is not one of the types we are expecting, we can print
throw Errors.OutOfStock information about it. The information is stored in a constant called error
} else { that we can read inside the catch block.
totalLamps = totalLamps - amount
}
} Listing 3-192: Getting information about the error
}
var mystock = Stock() 
enum Errors: String, Error {
do { case OutOfStock = "Hello"
try mystock.sold(amount: 8) }
} catch Errors.OutOfStock { struct Stock {
print("We do not have enough lamps") var totalLamps = 5
} mutating func sold(amount: Int) throws {
 if amount > totalLamps {
throw Errors.OutOfStock
} else {
The code in Listing 3-191 expands the previous example to handle the error totalLamps = totalLamps - amount
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 }
var mystock = Stock() code checks if there are enough lamps before calling the sold() method, so
do { we know that the instruction will never throw the OutOfStock error.
try mystock.sold(amount: 8)
} catch {
print(error) // OutOfStock
Listing 3-194: Ignoring the errors
} 
 enum Errors: Error {
case OutOfStock
On the other hand, if we do not care about the error, we can force the try }
struct Stock {
keyword to return an optional with the syntax try?. If the method throws an var totalLamps = 5
error, the instruction returns nil, and therefore we can avoid the use of the mutating func sold(amount: Int) throws {
if amount > totalLamps {
do catch statements.
throw Errors.OutOfStock
} else {
Listing 3-193: Catching errors with try? totalLamps = totalLamps - amount
}
 }
enum Errors: Error { }
case OutOfStock var mystock = Stock()
}
struct Stock { if mystock.totalLamps > 3 {
var totalLamps = 5 try! mystock.sold(amount: 3)
mutating func sold(amount: Int) throws { }
if amount > totalLamps { print("Lamps in stock: \(mystock.totalLamps)")
throw Errors.OutOfStock 
} else {
totalLamps = totalLamps - amount
}
}
}
var mystock = Stock()
try? mystock.sold(amount: 8) // nil

The instruction at the end of Listing 3-193 returns the value nil if the
method throws an error, or an optional with the value returned by the
method if everything goes right.
Sometimes, we know beforehand that a throwing method is not going to
throw an error and therefore we want to avoid writing unnecessary code.
In cases like this, we can use the syntax try!. For instance, the following

Results }
}
 

The sold() method in Listing 3-195 now returns a Result value of type <Int,
Sometimes we need to return more than just an error. For this purpose,
Errors>, so if an error occurs, the method can return a failure() value with the
the Swift Standard Library defines the Result enumeration. The enumeration
associated value OutOfStock, but if we have enough lamps to fulfil the order,
defines two cases with associated values to use in case of success or
we can return a success() value with the remaining number of lamps. The
failure, called success() and failure(). The Result enumeration is generic, which
result can be read by a switch statement. We check whether the value
means that the data types of the associated values can be anything we
returned by the method is failure() or success(), get the associated value with
want. For instance, in the following examples we define a Result
a let statement, and proceed accordingly. In this case, there are enough
enumeration of type <Int, Errors> to return an integer and the OutOfStock
lamps available, so a message is printed on the console with the remaining
error defined in the previous example.
stock.
Instead of using a switch statement, we can use the following method
Listing 3-195: Returning an error with a Result enumeration
defined by the Result enumeration.

enum Errors: Error {
case OutOfStock get()—This method returns the associated value of the success() case
} or throws an error with the associated value of the failure() case.
struct Stock {
var totalLamps = 5
The only purpose of the get() method is to simplify the code. Now, instead
mutating func sold(amount: Int) -> Result<Int, Errors> {
if amount > totalLamps { of a switch statement, we can use a do catch.
return .failure(.OutOfStock)
} else {
totalLamps = totalLamps - amount
Listing 3-196: Processing an error with the get() method
return .success(totalLamps) 
} enum Errors: Error {
} case OutOfStock
} }
var mystock = Stock() struct Stock {
var totalLamps = 5
let result = mystock.sold(amount: 3)
switch result { mutating func sold(amount: Int) -> Result<Int, Errors> {
case .success(let stock): if amount > totalLamps {
print("Lamps in stock: \(stock)") return .failure(.OutOfStock)
case .failure(let error): } else {
if error == .OutOfStock { totalLamps = totalLamps - amount
print("Error: Out of Stock") return .success(totalLamps)
} else { }
print("Error")
}
} CHAPTER 4 - INTRODUCTION TO
var mystock = Stock()

let result = mystock.sold(amount: 2)


FRAMEWORKS
do {
let stock = try result.get()
print("Lamps in stock: \(stock)")
} catch Errors.OutOfStock {
print("Error: Out of Stock")
}

The result is the same, but now all we need to do is to call the get() method.
If the method doesn't return an error, the remaining stock is printed on the
console, otherwise, the catch block is performed, and an error is printed
instead.

4.1 Frameworks Importing Frameworks


 

The programming tools introduced in previous chapters are not enough to The Swift Standard Library we have been using in previous chapters is
build professional applications. Creating an app requires accessing complex automatically loaded for us and available everywhere in our code, but
technologies and performing repetitive tasks that involve hundreds or even when we require the use of other frameworks, we must indicate it to the
thousands of lines of code. Faced with this situation, developers always system. This is done by adding the import instruction at the beginning of
implemented pre-programmed codes that perform common tasks. These each file followed by the name of the framework we want to include
pieces of code are organized according to their purpose in what we know (e.g., import Foundation). Once the framework is imported, it is included in
as frameworks. our file, giving us access to all the structures, classes, functions, and any
Frameworks are libraries (pre-programmed code) and APIs (Application of the values defined in its code.
Programming Interfaces) that we can use to add functionality to our
applications. This includes managing databases, creating graphics on the
screen, storing files, accessing resources on the Web, sharing data online,
and more. These frameworks are essential for creating professional
applications for Apple devices and are therefore part of the SDK (Software
Development Kit) included with Xcode.

Do It Yourself: The examples in this chapter were designed to be


tested in Playground. You just need to create a Playground file with a
Blank template and then replace the code with the example you
want to try.
4.2 Foundation More Standard Functions
 

Foundation is one of the oldest frameworks provided by Apple. It was


written in Objective-C and developed by Steve Jobs’s second company
NeXT. It was created to manage basic tasks and store data. The framework
provides its own data types (structures and classes) to store any value we
want, including numbers and strings of characters, arrays and dictionaries,
and a primary class called NSObject with basic behavior that every other
class inherits from. Most of these definitions are now obsolete, replaced by
Swift’s data types, but others remain useful, as we will see next.

As we have seen in Chapter 3, the Swift Standard Library includes a few 


standard functions, such as print() or abs(), but others are provided by import Foundation
frameworks like Foundation. The following are some of the basic functions let square = sqrt(4.0)
and structures available when we import the Foundation framework. let power = pow(2.0, 2.0)
let maximum = max(square, power)

pow(Float, Float)—This function returns the result of raising the print("The maximum value is \(maximum)") // "The maximum value is 4.0"

first value to the power of the second value. The arguments may be
numbers of type Float or Double.
The first thing we do in the code of Listing 4-1 is to import the
sqrt(Float)—This function returns the square root of the value of its Foundation framework. After this, we can implement any of the tools
argument. The argument may be of type Float or Double. defined inside the framework, including the basic functions introduced
log(Float)—This function returns the natural logarithm of a value. above. This example gets the square root of 4.0, calculates 2.0 to the
Similar functions are log2(), log10(), log1p(), and logb(). It can take a value power of 2.0, and compares the results using the max() function from the
of type Float or Double. Swift Standard Library.
sin(Float)—This function returns the sine of a value. Similar
functions are asin(), sinh(), and asinh(). The argument may be of type Float
or Double.
cos(Float)—This function returns the cosine of a value. Similar
functions are acos(), cosh(), and acosh(). The argument may be of type
Float or Double.

tan(Float)—This function returns the tangent of a value. Similar


functions are atan(), atan2(), tanh(), and atanh(). The argument may be of
type Float or Double.
UUID()—This is an initializer that returns a structure of type UUID
with a unique value that can be used to identify instances of custom
structures, classes and more.

The application of these functions is straightforward, as shown in the


following example.

Listing 4-1: Applying math functions


Strings offered by the NSString class from the String structure, including the
following properties and methods.

capitalized—This property returns a string with the first letter of
Foundation defines a class called NSString to store and manage strings of
every word in uppercase.
characters. The String structure offered by the Swift Standard Library for this
same purpose adopts most of its functionality, turning the class obsolete, length—This property returns the number of characters in the string
but because Swift coexists with old frameworks and data types, NSString of an NSString object. (For String values, we should use the count
objects are still required in some circumstances. The NSString class includes property instead.)
several Initializers to create these objects. The one usually implemented in localizedStringWithFormat(String, Values)—This type method
Swift takes an argument called string with the string of characters we want
creates a string from the string provided by the first argument and the
to assign to the object.
values provided by the second argument. The first argument is a
template used to create the string, and the second argument is the
Listing 4-2: Creating an NSString object

list of values we want to include in the string separated by comma.
import Foundation contains(String)—This method returns a Boolean value that
indicates whether or not the string specified by the argument was
var text: NSString = NSString(string: "Hello")
print(text) // "Hello" found inside the original string.

trimmingCharacters(in: CharacterSet)—This method erases the
If we already have a String value in our code, we can cast it into an NSString characters indicated by the in argument at the beginning and the end
object with the as operator. of the string and returns a new string with the result. The argument is
a CharacterSet structure with type properties to select the type of
Listing 4-3: Casting a String value into an NSString object characters we want to remove. The most frequently used properties
 are whitespaces (spaces) and whitespacesAndNewlines (spaces and new line
import Foundation characters).
var text = "Hello World" compare(String, options: CompareOptions, range: Range?,
var newText = text as NSString
print(newText) // "Hello World" locale: Locale?)—This method compares the original string with the
 string provided by the first argument and returns an enumeration of
type ComparisonResult with a value corresponding to the lexical order of
A String structure can be turned into an NSString object with the as operator
the strings. The orderedSame value is returned when the strings are
because they are interconnected. It is said that the String structure bridges
equal, the orderedAscending value is returned when the original string
with the NSString class. This means that we can access the functionality

precedes the value of the first argument, and the orderedDescending search. Except for the first argument, the rest of the argument s are
value is returned when the original string follows the value of the first optional.
argument. The options argument is a property of the CompareOptions
structure. The properties available are caseInsensitive (it considers As we already mentioned, the String structure is bridged to the NSString
lowercase and uppercase letters to be the same), literal (performs a class and therefore we can call these methods from String values, but
byte-to-byte comparison), diacriticInsensitive (ignores diacritic marks because they are defined in the NSString class, we still must import the
such as the visual stress on vowels), widthInsensitive (ignores the width Foundation framework to be able to use them. Some of them are like those
difference in characters that occurs in some languages), and offered by the String structure but allow us to perform additional
forcedOrdering (the comparison is forced to return orderedAscending or
operations on the values. For instance, we can incorporate values into
orderedDescending values when the strings are equivalent but not strictly
strings with string interpolation, but the localizedStringWithFormat() method
equal). The range argument defines a range that determines the offers a different approach. This method takes a string with placeholders
and replaces them with a list of values. The placeholders are declared with
portion of the original string we want to compare. Finally, the locale
the % symbol followed by a character that represents the type of value we
argument is a Locale structure that defines localization. Except for the
want to include. For example, if we want to replace the placeholder by an
first argument, the rest of the arguments are optional.
integer, we must use the characters %d.
caseInsensitiveCompare(String)—This method compares the
original string with the string provided by the argument. It works Listing 4-4: Creating a formatted string
exactly like the compare() method but with the option caseInsensitiveSearch 
set by default. import Foundation

range(of: String, options: CompareOptions, range: Range?, var age = 44


locale: Locale?)—This method searches for the string specified by var mytext = String.localizedStringWithFormat("My age is %d", age)
print(mytext) // "My age is 44"
the first argument and returns a range to indicate where the string 
was found or nil in case of failure. The options argument is a property
of the CompareOptions structure. The properties available for this There are different placeholders available. The most frequently used are
method are the same we have for the compare() method, with the %d for integers, %f for floating-point numbers, %g to remove redundant 0
(zeros), and %@ for objects and structures. We can use any of these
difference that we can specify three more: backwards (searches from
characters and as many times as necessary. This is like what we would get
the end of the string), anchored (matches characters only at the with string interpolation, but with this method we can also format the
beginning or the end, not in the middle), and regularExpression (searches values. For instance, we can determine the number of digits a value will
with a regular expression). The range argument defines a range that have by adding the amount before the letter.
determines the portion of the original string where we want to
Listing 4-5: Formatting numbers
 The compare() method takes a string, compares it to the original string, and
import Foundation returns a ComparisonResult value to indicate the order. The ComparisonResult
enumeration contains three values: orderedSame, orderedDescending, and
let length = 12.3472
let total = 54 orderedAscending. After comparing the values of the fruit and search variables in
let decimals = String.localizedStringWithFormat("Decimals: %.2f", length) our example, the result variable contains one of these values according to
let digits = String.localizedStringWithFormat("Digits: %.5d", total)
print(decimals) // "Decimals: 12.35" the lexical order of the strings. In this case, the value "Orange" assigned to
print(digits) // "Digits: 00054" fruit is bigger (follows alphabetically) the value "Apple" assigned to search, so

the value returned is orderedDescending (the order is descending from fruit to
search).
The code in Listing 4-5 formats two numbers, a double and an integer. The
The compare() method implemented in Listing 4-6 and the == operator
double is processed with the %.2f placeholder, which means that the value
studied in Chapter 2 consider a lowercase string different from an
is going to be rounded to two decimals after the point, and the integer is
processed with the %.5d placeholder, which means that the number in the uppercase string. Adding an option to the compare() method we can
string is going to contain a total of five digits. compare two strings without considering lowercase or uppercase letters.
Other methods provided by the NSString class perform operations that
are already available for String values, but they produce a more Listing 4-7: Comparing String values with options

comprehensive result. For example, the compare() method compares strings
import Foundation
like the == operator, but the value returned is not just true or false.
var fruit = "Orange"
Listing 4-6: Comparing String values var search = "ORANGE"

 var result = fruit.compare(search, options: .caseInsensitive)


import Foundation switch result {
case .orderedSame:
var fruit = "Orange" print("The values are equal") // "The values are equal"
var search = "Apple" case .orderedDescending:
print("Fruit follows Search")
var result = fruit.compare(search) case .orderedAscending:
switch result { print("Fruit precedes Search")
case .orderedSame: }
print("Fruit and Search are equal") 
case .orderedDescending:
print("Fruit follows Search") // "Fruit follows Search"
case .orderedAscending:
The strings stored in the fruit and search variables in Listing 4-7 are different,
print("Fruit precedes Search") but because of the caseInsensitive option, they are considered equal. This
}

type of comparison is very common, which is why the class includes the

caseInsensitiveCompare()method that all it does is calling the compare() method 


with the caseInsensitive option already set. import Foundation
Despite this being the most common scenario, we can perform more var text = "The Suitcase is Black"
precise comparison by providing the range of characters we want to var search = "black "
compare. search = search.trimmingCharacters(in: .whitespacesAndNewlines)

var range = text.range(of: search, options: .caseInsensitive)


Listing 4-8: Comparing only a range of characters if let rangeToReplace = range {
text.replaceSubrange(rangeToReplace, with: "Red")

}
import Foundation print(text) // "The Suitcase is Red"

var phone = "905-525-6666"
var search = "905"
The range() method returns an optional value that contains the range where
var start = phone.startIndex the string was found or nil in case of failure. In Listing 4-9, we search for the
var end = phone.firstIndex(of: "-")
value of the search variable inside the text variable and check the optional
if let endIndex = end { value returned. When we have a range to work with (which means that the
let result = phone.compare(search, options: .caseInsensitive, range: start..<endIndex) value was found) we use it to call the replaceSubrange() method of the String
if result == .orderedSame {
print("The area code is the same") // "The area code is the same" structure to replace the characters in the range with the string "Red" (see
} else { Listing 3-67). Notice that because search values are usually provided by the
print("The area code is different")
}
user, we trim the value of the search variable with the trimmingCharacters()
} method to make sure that there are no space characters at the beginning

or the end of the string (the two spaces after the word "black" are
removed).
This example compares only the initial characters of a string to check the
area code of a phone number. The code defines a range that goes from the
first character of the phone variable to the position before the - character.
This range is provided to the compare() method and in consequence the
value of the search variable is compared only against the first three
characters.
We can also use ranges to search for strings using the range() method. This
method searches for a string inside another string and returns a range that
determines where the string was found.

Listing 4-9: Searching and replacing characters in a string


Ranges The initializer implemented in this example is for countable ranges. If
we work with string indexes, we must use the initializer defined for strings.

This is because the String structure works with Unicode characters while
NSString objects work with a less comprehensive character encoding called
Although Swift includes range structures to store ranges of values, some
UTF-16. Working with different character encodings means that the space
frameworks programmed in Objective-C still implement an old Foundation
the characters occupy in memory varies. A range that represents a series of
class called NSRange. The NSRange class is slightly different than the Swift's
characters in a String value may differ from a range that represents the
Range structure. Instead of storing the initial and final values of the range,
same series of characters in an NSString value. The following example
NSRange objects store the initial value and the length of the range.
illustrates how to work with this initializer.
NSRange(Range)—This initializer creates an NSRange object from a
Listing 4-11: Converting a range of string indexes
Range value.

NSRange(Range, in: String)—This initializer creates an NSRange import Foundation
object to represent a Range structure with string indexes.
let text = "Hello World"
Range(NSRange)—This initializer creates a Range structure from an if let start = text.firstIndex(of: "W") {
let newRange = NSRange(start..., in: text)
NSRange value. print("Initial: \(newRange.location)") // "Initial: 6"
Range(NSRange, in: String)—This initializer creates a Range print("Length: \(newRange.length)") // "Length: 5"
}
structure to represent an NSRange object with string indexes. 

The NSRange class also includes two properties to retrieve its values:
and length. The following example initializes an NSRange object from a
location
Swift range and prints its values.

Listing 4-10: Creating and reading an NSRange value



import Foundation

let range = NSRange(4..<10)


print("Initial: \(range.location)") // "Initial: 4"
print("Length: \(range.length)") // "Length: 6"

Numbers 

 Besides its own data type, Foundation also provides the means to format
numbers. Every time we print a number, all the digits are shown on the
Foundation offers a class called NSNumber to represent and store numbers. screen, including all the decimal digits. In Listing 4-4, we explained how to
With the introduction of the Swift’s primitive data types, the use of this specify how many digits of a number we want to include in a string using
class is no longer necessary, but there are a few old frameworks that still placeholders (e.g., %.2f), but this is not customizable enough. To provide a
require these types of values. The class includes the following initializer. better alternative, the framework includes the following formatting
method.
NSNumber(value: Value)—This initializer creates an NSNumber
object with the value specified by the value argument. The argument formatted(FormatStyle)—This method formats the number
may be a value of any of the data types available in Swift for numbers. according to the styles provided by the argument.

The class also provides properties to perform the opposite operation, To format a number, we must call this method from the instance with the
getting Swift data types from NSNumber objects. The following are the most styles we want to apply to it. The styles are defined by a structure that
frequently used. conforms to the FormatStyle protocol. For numbers, the framework defines
the IntegerFormatStyle and the FloatingPointFormatStyle structures. These
intValue—This property returns an Int value with the object’s structures include the following methods to style a number.
number.
floatValue—This property returns a Float value with the object’s precision(Precision)—This method defines the number of digits
number. included in the integer and decimal parts of the number. The
argument is a Precision structure, which includes the integerLength(Int)
doubleValue—This property returns a Double value with the object’s
and fractionLength(Int) methods to determine the number of digits in the
number.
integer and decimal parts, and the integerAndFractionLength(integer: Int,
fraction: Int) method to determine both.
The following example shows how to create NSNumber objects and how to
get them back as Swift data types to perform operations. rounded(rule: FloatingPointRoundingRule)—This method rounds
the number to the nearest value. The rule argument is an
Listing 4-12: Working with NSNumber objects enumeration with the values up, down, awayFromZero,
 toNearestOrAwayFromZero, toNearestOrEven, and towardZero.
import Foundation grouping(Grouping)—This method determines if the digits of a
var mynumber = NSNumber(value: 35) number are going to be separated in groups (e.g., 9,000,000). The
var mydouble = mynumber.doubleValue * 2 // 70
argument is a structure with the properties automatic (default) and send to this method the value returned by the fractionLength() method,
never. which formats the number with 2 decimal digits. As a result, we get a string
with the value "32.57" (the value is rounded up).
notation(Notation)—This method determines the number's
Styles can be concatenated, one after another, with dot notation. For
notation. The argument is a structure with the properties automatic
instance, in the previous example, the number was rounded up by default,
(default), compactName, and scientific.
but we can change this behavior by applying the rounded(rule:) method, as
sign(strategy: SignDisplayStrategy)—This method determines if shown next.
the sign will be included (+ and -). The strategy argument is a
SignDisplayStrategy structure, which includes the automatic (default) and Listing 4-14: Rounding a number
never properties, and also the always(includingZero: Bool) method to 

determine if the sign is displayed or not. import Foundation

decimalSeparator(strategy: DecimalSeparatorDisplayStrategy) let mynumber: Double = 32.56789


let text = mynumber.formatted(.number.precision(.fractionLength(2)).rounded(rule: .down))
—This method determines if a separator is going to be included after print(text) // "32.56"
the number. The strategy argument is a structure with the properties 

automatic (default) and always.


In this example, the rounded(rule:) method is called after the number is
The FormatStyle protocol defines the number property, which contains an formatted with 2 decimal digits, so the rest of the digits are rounded down.
instance of the IntegerFormatStyle or the FloatingPointFormatStyle structures, The result is a string with the value "32.56".
depending on the number's data type. From this instance, we can apply all The grouping, notation, and decimal separator styles usually apply to large
the styles we want to a number. numbers. For instance, by default, the digits of large numbers are
separated in groups, as in 32,000,000, but we can change this behavior
Listing 4-13: Formatting a number with the grouping() method.

import Foundation
Listing 4-15: Disabling grouping

let mynumber: Double = 32.56789 import Foundation
let text = mynumber.formatted(.number.precision(.fractionLength(2)))
print(text) // "32.57" let mynumber: Int = 32000000
 let text = mynumber.formatted(.number.grouping(.never))
print(text) // "32000000"
The styles are provided one by one with dot notation. We first get the 

styling structure from the number property (in this case, the number is a
Double so the value of the property is an instance of the
We can also show the sign in front of the number (+ or -). In the following
FloatingPointFormatStyle structure). Next, we call the precision() method, and
example, we always show the sign except when the number is equal to 0.

let text = mynumber.formatted(.currency(code: "CAD"))


print(text) // "CA$32.55"
Listing 4-16: Adding the sign 

import Foundation

let mynumber: Int = 32000000


let text = mynumber.formatted(.number.sign(strategy: .always(includingZero: false)))
print(text) // "+32,000,000"

In addition to number, the FormatStyle protocol defines the percent property to


style the number as a percentage, and the currency(code:) method to format
monetary values. The percent property is a Percent structure that all it does is
to add the % sign to the number.

Listing 4-17: Formatting the number as a percentage value



import Foundation

let mynumber: Double = 32.55


let text = mynumber.formatted(.percent)
print(text) // "32.55%"

On the other hand, the currency(code:) method can produce a number with
any format and currency symbol we want. The currency is defined by the
string assigned to the argument. There are values for any currency
available. For instance, the USD string is for American Dollars, the CAD
string is for Canadian Dollars, EUR for Euros, and so on. The following
example gets the number expressed in Canadian dollars (for more values,
visit our website and follow the links for this chapter).

Listing 4-18: Formatting currency values



import Foundation

let mynumber: Double = 32.55


Dates If the initializer requires an interval, as those in the code of Listing 4-19, the
value is specified in seconds. An easy way to calculate the seconds is

multiplying every component. For example, the date for the nextday object
created in our example is calculated adding 1 day to the current date. The
Foundation defines multiple classes and structures to create and process
number of seconds in 1 day are calculated by multiplying the 24 hours of
dates, including Date, Calendar, DateComponents, DateInterval, Locale, and TimeZone.
the day by the 60 minutes in an hour by the 60 seconds in a minute (24 *
The data type in charge of creating the structure to store the actual date is
60 * 60). For the tendays object, we apply the same technique. This
Date. The following are some of its initializers.
initializer adds the interval to a specific date (nextday). The seconds are
calculated by multiplying the components, albeit this time it multiplies the
Date()—This initializer creates a Date structure with the system’s previous result by -10 to get a date 10 days before nextday (we will see
current date. better ways to add components to a date later).
Date(timeIntervalSinceNow: TimeInterval)—This initializer
creates a Date structure with a date calculated from the addition of the IMPORTANT: These methods require a value of type Double to
current date plus the time specified by the timeIntervalSinceNow declare the interval in seconds, but instead of Double the framework
argument. The argument is a value of type TimeInterval that indicates calls it TimeInterval. This is a typealias (an alternative name for an
how many seconds the date is from the initial date. existing type). Once defined, aliases are used exactly like regular data
types. To create your own type aliases, you can use the instruction
Date(timeInterval: TimeInterval, since: Date)—This initializer typealias (e.g., typealias myinteger = Int).
creates a Date structure with a date calculated from the addition of the
date specified by the since argument plus the time specified by the Besides the initializers, the class also includes type properties that return
timeInterval argument. The argument is a value of type TimeInterval special dates. Some of these properties produce values that are useful to
that indicates how many seconds the date is from the initial date. set limits and sort lists.

The following example shows different ways to initialize a date. distantFuture—This type property returns a Date structure with a
value that represents a date in a distant future.
Listing 4-19: Storing dates with Date structures
distantPast—This type property returns a Date structure with a value

that represents a date in a distant past.
import Foundation

var currentdate = Date() The Date structure also includes properties and methods to calculate and
var nextday = Date(timeIntervalSinceNow: 24 * 60 * 60)
var tendays = Date(timeInterval: -10 * 24 * 3600, since: nextday) compare dates. The following are the most frequently used.

timeIntervalSinceNow—This property returns a TimeInterval value var event = Date(timeIntervalSinceNow: Double(days) * 24 * 3600)

representing the difference in seconds between the date in the Date if today.compare(event) == .orderedAscending {
structure and the current date. let interval = event.timeIntervalSince(today)
print("We have to wait \(interval) seconds")
compare(Date)—This method compares the date in the Date }

structure with the date specified by the argument and returns an
enumeration of type ComparisonResult with a value corresponding to the The dates in Date structures are not associated to any calendar. This means
temporal order of the dates. The possible values are orderedSame (the that to get the components in a date (year, month, day, etc.) we must
dates are equal), orderedAscending (the date is earlier than the value), decide first in the context of which calendar the date is going to be
and orderedDescending (the date is later than the value). interpreted. The calendar for a date is defined by the Calendar structure.
timeIntervalSince(Date)—This method compares the date in the This structure provides properties and methods to process a date
Datestructure with the date specified by the argument and returns the according to a specific calendar (Gregorian, Buddhist, Chinese, etc.). To
initialize a Calendar structure, we have the following initializer and type
interval between both dates in seconds.
property.
addingTimeInterval(TimeInterval)—This method adds the
seconds specified by the argument to the date in the Date structure Calendar(identifier: Identifier)—This initializer creates a Calendar
and returns a new Date structure with the result. structure with the calendar specified by the argument. The identifier
addTimeInterval(TimeInterval)—This method adds the seconds argument is a property of a structure called Identifier defined inside the
specified by the argument to the date in the Date structure and stores Calendar structure. The properties available are gregorian, buddhist, chinese,
the result in the same structure. coptic, ethiopicAmeteMihret, ethiopicAmeteAlem, hebrew, ISO8601, indian, islamic,
islamicCivil, japanese, persian, republicOfChina, islamicTabular and
Comparing dates and calculating the intervals between dates is a constant islamicUmmAlQura.
requirement in app development. The following example compares the
current—This type property returns a structure with the current
current date with a date calculated from a specific number of days. If the
calendar set in the system.
resulting date is later than the current date, the code prints a message on
the console to show the time remaining in seconds.
A Calendar structure includes the following properties and methods to
manage the calendar and to get and set new dates.
Listing 4-20: Comparing two dates

identifier—This property returns the value that identifies the
import Foundation
calendar.
var days = 7

var today = Date()


locale—This property sets or returns the Locale structure used by the second,and weekday to read and set the values of the components. The
Calendarstructure to process dates. The value by default is the Locale following example combines these tools to get the year of the current
structure defined by the system. date.
timeZone—This property sets or returns the TimeZone structure used
Listing 4-21: Extracting components from a date
by the Calendar structure to process dates. The value by default is the

TimeZone structure set by the system.
import Foundation
dateComponents(Set, from: Date)—This method returns a var today = Date()
DateComponentsstructure with the components indicated by the first let calendar = Calendar.current
var components = calendar.dateComponents([.year], from: today)
argument from the date indicated by the from argument. The first
print("The year is \(components.year!)")
argument is a set with properties of a structure called Unit that 
represent each component (year, month, day, hour, minute, second, etc.).
In Listing 4-21, we get a reference to the calendar set in the system from
dateComponents(Set, from: Date, to: Date)—This method
the current property and then use the dateComponents() method to get the
returns a DateComponents structure with the components indicated by
year component of the current date.
the first argument, which values represent the difference between the
Several components may be retrieved at once by adding the corresponding
dates specified by the from and to arguments. The first argument is a
properties to the set. The following example gets the year, month, and day
set with properties of a structure called Unit that represent each of the current date.
component. The most frequently used are year, month, day, hour, minute,
and second. Listing 4-22: Extracting multiple components from a date

date(byAdding: DateComponents, to: Date)—This method
import Foundation
returns a Date structure with the value obtained by adding the
components indicated by the byAdding argument to the date var today = Date()
let calendar = Calendar.current
indicated by the to argument. var comp = calendar.dateComponents([.year, .month, .day], from: today)
print("Today \(comp.day!)-\(comp.month!)-\(comp.year!)")
date(from: DateComponents)—This method returns a date 
created from the components provided by the from argument. The
value returned is a Date structure. 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
DateComponents structure include the properties year, month, day, hour, minute,
Listing 4-23: Creating a new date from single components

import Foundation Listing 4-25: Adding components to a date


let calendar = Calendar.current 
var comp = DateComponents() import Foundation
comp.year = 1970
comp.month = 8 let id = Calendar.Identifier.gregorian
comp.day = 21 let calendar = Calendar(identifier: id)
var comp = DateComponents()
var birthday = calendar.date(from: comp) // "Aug 21, 1970, 12:00 AM" comp.day = 120

var today = Date()
var appointment = calendar.date(byAdding: comp, to: today)
The date(from:) method of the Calendar structure returns a new date with the 
values provided by the DateComponents structure. The components which
values are not explicitly defined take values by default (e.g., 12:00 AM). The date() method implemented in Listing 4-25 adds components to a date
Generating a new date requires a specific calendar. For example, in the and returns a new Date structure with the result. The component day was
code of Listing 4-23, the values of the components are declared with the set to 120. The date() method takes this value and adds it to the date in the
format established by the Gregorian calendar. In this case, we rely on the today structure (the current date), and returns the result.

calendar returned by the system, but if we want to use the same calendar 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
no matter where the app is executed, we must set it ourselves from the
the days remaining for an event to begin. The Calendar structure includes a
Calendar initializer.
version of the dateComponents() method that allows us to compare two dates
Listing 4-24: Using a Gregorian calendar and get the difference expressed in a specific component.

Listing 4-26: Comparing dates
import Foundation

let id = Calendar.Identifier.gregorian import Foundation
let calendar = Calendar(identifier: id)
let calendar = Calendar.current
var comp = DateComponents() var comp = DateComponents()
comp.year = 1970 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!)")
}
 the argument.

This example calculates the days between a birthdate and the current date. intersection(with: DateInterval)—This method returns a
The value returned by the date() method used to generate the birthdate value with the interval in which the original interval and the
DateInterval

returns an optional, so we unwrap it before calculating the difference. We one provided by the with argument overlap.
assign this value to the olddate constant and then compare it with the
current date. The number of days between the dates is returned and A typical use of the DateInterval structure is to create an interval from two
printed on the console. dates and check if a specific date falls within the interval, as in the
Another way to specify intervals between dates is with the DateInterval following example.
structure. This structure allows us to create an interval with Date values.
The following are its initializers. Listing 4-27: Finding a date in an interval

DateInterval(start: Date, end: Date)—This initializer creates a import Foundation

structure with the interval between the values provided by


DateInterval let calendar = Calendar.current
the start and end arguments.
var components = DateComponents()
DateInterval(start: Date, duration: TimeInterval)—This components.year = 1970
components.month = 8
initializer creates a DateInterval structure with an interval that starts at components.day = 21
the date specified by the start argument and last as long as the time var birthday = calendar.date(from: components)

specified by the duration argument. components.year = 2020


components.month = 8
components.day = 21
The DateInterval structure also offers the following properties and methods. var future = calendar.date(from: components)

if birthday != nil && future != nil {


start—This property sets or returns the initial Date of the interval. let today = Date()
end—This property sets or returns the final Date of the interval. let interval = DateInterval(start: birthday!, end: future!)
if interval.contains(today) {
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 a FormatStyle structure to format the date any way we want. The following
Date structure defines two versions of the formatted() method for this are some of the methods included for customization.
purpose.
day(Day)—This method includes the day. The argument defines the
formatted(date: DateStyle, time: TimeStyle)—This methods style for the day. It is a structure with the properties defaultDigits,
formats the date with the styles specified by the arguments. The date ordinalOfDayInMonth, and twoDigits.
argument defines the style for the date. It is a structure with the type month(Month)—This method includes the month. The argument
properties abbreviated, complete, long, numeric, and omitted. And the time defines the style for the month. It is a structure with the properties
argument defines the style for the time. It is a structure with the type abbreviated, defaultDigits, narrow, twoDigits, and wide.
properties complete, omitted, shortened, and standard.
year(Year)—This method includes the year. The argument defines
formatted(Date.FormatStyle)—This method formats the date with the style for the year. It is a structure with the properties defaultDigits
the styles specified by the argument. The argument is a FormatStyle and twoDigits.
structure defined by the Date structure.
hour(Hour)—This method includes the hour. The argument defines
the style for the hour. It is a structure with the properties
If all we need is a standard format, we can call the formatted(date:, time:)
defaultDigitsNoAMPM and twoDigitsNoAMPM.
method with the styles we want for the date and time. The method takes
these values and returns a string with a date in the format defined by the minute(Minute)—This method includes the minutes. The argument
current locale (the user's language and location). defines the style for the minutes. It is a structure with the properties
defaultDigits and twoDigits.
Listing 4-28: Formatting dates

second(Second)—This method includes the seconds. The argument
import Foundation defines the style for the seconds. It is a structure with the properties
defaultDigits and twoDigits.
let mydate = Date.now
let text = mydate.formatted(date: .abbreviated, time: .omitted) weekday(Weekday)—This method includes the weekday. The
print(text) // "Jun 18, 2021"

argument defines the style for the day. It is a structure with the
properties abbreviated, narrow, oneDigit, short, twoDigits, and wide.
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.
locale—This property sets or returns the locale used to format the In this code, we implement the day(), month(), and hour() methods. The result
date. It is of type Locale. is a string with a date that includes the month (full name), the day, and the
hour ("June 18, 6 PM").
timeZone—This property sets or return the time zone used to
Notice that the order in which the methods are called doesn't matter. The
format the date. It is of type TimeZone.
date and time are always formatted with a standard format that depends
on the user's locale (language and country). This is because the formatted()
Although we can create our own FormatStyle structure, the structure
method processes dates according to local conventions, including the
includes a type property called dateTime to return an instance with the
language, symbols, etc. This means that the components of a date are
calendar and standard values set by the device. If the configuration by
going to be interpreted according to the conventions currently set on the
default is enough, we can use this property to format the date, as shown
device. For example, the same date will look like this "Tuesday, August 6,
next.
2021" for a user in the United States and like this "2021 8 6 年 月 日 星期二
"
Listing 4-29: Specifying a custom format for a user in China. How dates are processed is determined by an object of
 the Locale structure. Every device has a Locale structure assigned by default,
import Foundation and our code will work with it unless we determine otherwise. To get a
reference to the current structure or create a new one, the Locale structure
let mydate = Date.now
includes the following initializer and type property.
let text = mydate.formatted(.dateTime.weekday(.wide))
print(text) // "Friday"
 Locale(identifier: String)—This initializer creates a Locale structure
configured for the region determined by the value of the argument.
The code in Listing 4-29 calls the weekday() method from the FormatStyle
The argument is a string that represents a language and a region (e.g.,
structure returned by the dateTime property to get the day of the week. In
en_US for the United States, zh_CN for China).
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 current—This type property returns the Locale structure assigned by
we can add more by concatenating the methods with dot notation, as we default to the device or defined by the user in the Settings app.
did before for numbers.
The FormatStyle structure includes the following method to format a date for
Listing 4-30: Including multiple date components
a locale.

import Foundation
locale(Locale)—This method specifies the locale to use by the
let mydate = Date.now formatter.
let text = mydate.formatted(.dateTime.day().hour().month(.wide))
print(text) // "June 18, 6 PM"
 Although it is recommended to use the Locale structure set by the system
and keep the values by default, there are times when our application must

present the information with a specific configuration. For example, we may our device), but we can define a different one as we did with the Locale
need to create an application that always shows dates in Chinese, no structure. To get a reference to the current structure or create a new one,
matter where the user is located. We can do this by defining a Locale the TimeZone structure includes the following initializer and type property.
structure and then include the locale() method in the formatter with this
value. TimeZone(identifier: String)—This initializer creates a TimeZone
structure configured for the time zone determined by the value of the
Listing 4-31: Specifying a different locale identifier argument. The argument is a string that represents the

name of the time zone (e.g., "Europe/Paris", "Asia/Bangkok").
import Foundation
current—This type property returns the TimeZone structure assigned
let mydate = Date.now
let chinaLocale = Locale(identifier: "zh_CN")
by default to the device or defined by the user in the Settings app.
let text = mydate.formatted(.dateTime.locale(chinaLocale).day().month().year())
年月 日
print(text) // "2021 6 18 "
The FormatStyle structure does not include a method to provide a specific

time zone to format the date. For this purpose, we must create a custom
This example creates a new Locale structure with the zh_CN identifier, instance and then assign the time zone to the structure's timeZone property,
which corresponds to China and the Chinese language, and then formats as in the following example.
the date with this locale and the day(), month(), and year() methods. The result
is a string with the date in Chinese ("2021 6 18 ").年月 日 Listing 4-32: Working with different time zones

import Foundation
IMPORTANT: The list of identifiers you can use to create a Locale
structure is extensive, but you can print the type property if let tokyoTimeZone = TimeZone(identifier: "Asia/Tokyo"), let madridTimeZone =
TimeZone(identifier: "Europe/Madrid") {
availableIdentifiers from the Locale structure to get an array with all the let mydate = Date.now
values available. let mytime = mydate.formatted(.dateTime.hour().minute().second())

var dateTimeStyle = Date.FormatStyle()


The date stored in a Date structure is not a date but the number of seconds dateTimeStyle.timeZone = tokyoTimeZone
let tokyoTime = mydate.formatted(dateTimeStyle.hour().minute().second())
between the date represented by the object and an arbitrary date in the
past (January 1st, 2001). To process these values and get the actual date, dateTimeStyle.timeZone = madridTimeZone
let madridTime = mydate.formatted(dateTimeStyle.hour().minute().second())
the Calendar structure needs to know the user's time zone. Foundation
includes the TimeZone structure to manage time zones. An object is assigned print("My Time: \(mytime)") // "My Time: 9:25:19 PM"
print("Tokyo Time: \(tokyoTime)") // "Tokyo Time: 10:25:19 AM"
by default to the system containing the time zone where the device is print("Madrid Time: \(madridTime)") // "Madrid Time: 3:25:19 AM"
located (that is why when we display a date it coincides with the date in }

Measurements
The code in Listing 4-32 creates two TimeZone structures, one for Tokyo's

time zone and another for Madrid's. If successful, we initialize a FormatStyle
structure and format the date twice, first for Tokyo and then for Madrid.
Some applications require the use of units of measurement, such as
Notice that the TimeZone structures are assigned to the timeZone property of
pounds, miles, liters, etc. Defining our own units present some challenges,
the FormatStyle structure before using the structure to format each date.
but Foundation includes the Measurement structure to simplify our work. This
structure includes two properties, one for the value and another for the
IMPORTANT: The list of names for the time zones is stored in a
unit. The initializer requires these two values to create the structure.
database. The TimeZone structure offers the knownTimeZoneIdentifiers
type property that you can print to see all the values available.
Measurement(value: Double, unit: Unit)—This initializer creates
a Measurement structure with the values specified by the value and unit
arguments. The unit argument is a property of a subclass of the
Dimension class.

The value declared for the Measurement structure is the number that
determines the magnitude, like 55 in 55 km, and the unit is a property of a
subclass of the Dimension class that represents the unit of measurement, like
km in 55 km. The Dimension class contains all the basic functionally required
for measurement but is through its subclasses that the units of
measurement are determined. Foundation offers multiple subclasses to
define units for different types of dimensions. The following are the most
frequently used.

UnitDuration—This subclass of the Dimension class defines the units


of measurement for duration (time). The subclass includes the
following properties to represent the units: seconds, minutes, and hours,
with seconds defined as the basic unit.
UnitLength—This subclass of the Dimension class defines the units of
measurement for length. The subclass includes the following
properties to represent the units: megameters, kilometers, hectometers,

decameters, meters, decimeters, centimeters, millimeters, micrometers, nanometers, converted(to: Unit)—This method converts the values of the
picometers, inches, feet, yards, miles, scandinavianMiles, lightyears, nauticalMiles, Measurementstructure to the unit specified by the to argument and
fathoms, furlongs, astronomicalUnits, and parsecs, with meters defined as the returns a new Measurement structure with the result. The argument is a
basic unit. property of a subclass of the Dimension class.
UnitMass—This subclass of the Dimension class defines the units of
measurement for mass. The subclass includes the following properties The initialization of a Measurement structure is simple, we just need to
to represent the units: kilograms, grams, decigrams, centigrams, milligrams, provide the value for the magnitude and the property that represents the
micrograms, nanograms, picograms, ounces, pounds, stones, metricTons, shortTons,
unit of measurement we want to use. The following example creates two
carats, ouncesTroy, and slugs, with kilograms defined as the basic unit.
structures to store a measurement of 30 centimeters and another of 5
pounds.
UnitVolume—This subclass of the Dimension class defines the units of
measurement for volume. The subclass includes the following Listing 4-33: Initializing Measurement structures
properties to represent the units: megaliters, kiloliters, liters, deciliters, 
centiliters, milliliters, cubicKilometers, cubicMeters, cubicDecimeters, import Foundation
cubicMillimeters, cubicInches, cubicFeet, cubicYards, cubicMiles, acreFeet, bushels,
var length = Measurement(value: 30, unit: UnitLength.centimeters) // 30.0 cm
teaspoons, tablespoons, fluidOunces, cups, pints, quarts, gallons, imperialTeaspoons, var weight = Measurement(value: 5, unit: UnitMass.pounds) // 5.0 lb

imperialTablespoons, imperialFluidOunces, imperialPints, imperialQuarts,
imperialGallons, and metricCups, with liters defined as the basic unit.
If the measurements are of the same dimension (e.g., length), we can
perform operations with their values. The Measurement structure allows the
The Measurement structure includes the following properties and methods to
operations +, -, *, /, and also the use of the comparison operators ==, !=, <, >,
access the values and convert them to different units.
<=, and >= to compare values. The following example adds two
measurements in centimeters.
value—This property sets or returns the structure's value. It is of type
Double. Listing 4-34: Adding the values of two Measurement structures
unit—This property sets or returns the structure's unit of 

measurement. It is represented by a property of a subclass of the import Foundation

Dimension class. var length = Measurement(value: 200, unit: UnitLength.centimeters)


var width = Measurement(value: 800, unit: UnitLength.centimeters)
convert(to: Unit)—This method converts the values of the
Measurementstructure to the unit specified by the to argument. The var total = length + width // 1000.0 cm

argument is a property of a subclass of Dimension.
If the units are different, the Measurement structure returned by the to our users. Fortunately, the Measurement structure defines the formatted()
operation is defined with the dimension's basic unit. For example, if we are method to format these values.
working with lengths, the basic unit is meters.
formatted(Measurement.FormatStyle)—This method formats
Listing 4-35: Adding two values of different units the measurement with the styles specified by the argument. The

argument is a FormatStyle structure defined in the Measurement structure.
import Foundation

var length = Measurement(value: 300, unit: UnitLength.meters) The formatted() method requires a FormatStyle structure to format the value.
var width = Measurement(value: 2, unit: UnitLength.kilometers)
The structure includes the following initializer and type method to create
var total = length + width // 2300.0 m an instance for every type of unit.

FormatStyle(width: UnitWidth, locale: Locale, usage:


The code in Listing 4-35 adds two lengths of different units (meters and
MeasurementFormatUnitUsage, numberFormatStyle:
kilometers). The system converts kilometers to meters and then performs
FloatingPointFormatStyle)—This initializer creates a FormatStyle
the addition, returning a Measurement structure with a value in meters (the
structure with the format set by the arguments. The width argument
default unit).
specifies how the unit is going to be displayed. It is a structure with
If we want everything to be performed in the same unit, we can convert a
the properties abbreviated, narrow, and wide. The locale argument
value to a different unit using the convert() or converted() methods. In the
specifies the locale. The usage argument specifies the purpose of the
following example, we convert the unit of the length variable to kilometers
formatted measurement. The structure to declare this value includes
and perform the addition again in kilometers.
properties for any type of measurement, including asProvided
Listing 4-36: Converting units (UnitType), food (UnitEnergy), general (UnitType), person: (UnitLength),
 personHeight (UnitLength), personWeight (UnitMass), road (UnitLength),

import Foundation weather (UnitTemperature), and workout (UnitEnergy). Finally, the


numberFormatStyle argument specifies the format of the value.
var length = Measurement(value: 300, unit: UnitLength.meters)
var width = Measurement(value: 2, unit: UnitLength.kilometers) measurement(width: UnitWidth, usage:
length.convert(to: UnitLength.kilometers)
MeasurementFormatUnitUsage, numberFormatStyle:
var total = length + width // 2.3 km FloatingPointFormatStyle)—This method returns a FormatStyle

structure with the format set by the arguments (the same required by
The values of a Measurement structure are printed as they are stored and the initializer).
with the units they represent, but this is usually not what we need to show

The FormatStyle structure also includes an additional initializer and method import Foundation

specific to format temperatures. let length = Measurement(value: 40, unit: UnitLength.kilometers)


let text = length.formatted(.measurement(width: .wide, usage: .asProvided))
print(text) // "40 kilometers"
FormatStyle(width: UnitWidth, locale: Locale, usage: 
MeasurementFormatUnitUsage, hidesScaleName: Bool,
numberFormatStyle: FloatingPointFormatStyle)—This initializer In this example, we have also included the usage argument with the
creates a FormatStyle structure with the format set by the arguments. asProvided value to tell the formatter to use the original units (kilometers). If

The width argument specifies how the unit is going to be displayed. It this argument is not declared, the formatter uses the value by default,
which formats the measurement with the configuration and locale set in
is a structure with the properties abbreviated, narrow, and wide. The
the device. For instance, if we specify the road value instead, the formatter
locale argument specifies the locale. The usage argument specifies
will format the value to represent a distance using the device's locale,
the purpose of the formatted measurement. The structure to declare
which for a device running in the United States means that the original
this value includes properties for any type of measurement. The ones value will be converted to miles.
available are asProvided (UnitType), food (UnitEnergy), general (UnitType),
person (UnitTemperature), personHeight (UnitLength), personWeight Listing 4-38: Formatting a measurement for a specific purpose
(UnitMass), road (UnitLength), weather (UnitTemperature), and workout 

(UnitEnergy). The hidesScaleName argument determines if the name import Foundation

of the unit is going to be displayed, and the numberFormatStyle let length = Measurement(value: 40, unit: UnitLength.kilometers)
argument specifies the format of the value. let text = length.formatted(.measurement(width: .wide, usage: .road))
print(text) // "25 miles"
measurement(width: UnitWidth, locale: Locale, usage: 

MeasurementFormatUnitUsage, hidesScaleName: Bool,


By default, the formatter rounds the number. That's why in this example
numberFormatStyle: FloatingPointFormatStyle)—This method
the result of converting 40 kilometers to miles is 25, when it should've
returns a FormatStyle structure with the format set by the arguments.
been 24.8548. If we want to specify a different format, we can add the
The arguments are the same required by the initializer. numberFormatStyle argument and implement the methods provided by
the IntegerFormatStyle and FloatingPointFormatStyle structures introduced before.
Most of the arguments in these initializers and methods are optional. If an For instance, we can specify a precision of 2 digits for the decimal part with
argument is not declared, the formatter uses values by default. For the fractionLength() method, as shown next.
instance, if we just want to show the full name of the unit, we can call the
measurement() method with the width argument and the value wide. Listing 4-39: Formatting the measurement's value

Listing 4-37: Formatting a measurement import Foundation

let length = Measurement(value: 40, unit: UnitLength.kilometers)
let text = length.formatted(.measurement(width: .wide, usage: .road, numberFormatStyle:
.number.precision(.fractionLength(2))))
print(text) // "24.85 miles"

The formatted() method in this example includes the width argument to get
the unit's full name, the usage argument to format the value to represent
distance, and the numberFormatStyle argument to format the value. The
value is expressed in miles again, but it is more accurate.
If our application must display a value always with the same unit of
measurement independently of the device's location, we can set a specific
locale. The formatted() method doesn't include an argument to designate a
locale, but the initializers included in the FormatStyle structure do. The
following example formats the measurements in Chinese, no matter where
the device is located.

Listing 4-40: Formatting a measurement for a specific locale



import Foundation

let length = Measurement(value: 40, unit: UnitLength.kilometers)


let chinaLocale = Locale(identifier: "zh_CN")
var format = Measurement<UnitLength>.FormatStyle(width: .wide, locale: chinaLocale,
usage: .asProvided)
let text = length.formatted(format)
公里
print(text) // "40.00 "

The code in Listing 4-40 initializes a FormatStyle structure with the locale
configured for China and then calls the formatted() method with this
structure to format the value. Notice that the FormatStyle structure is
defined inside the Measurement structure, which is a generic structure and
therefore we must specify the type of values the structure is going to
process. In this case, we are working with units of length so we must
specify the UnitLength data type.

Timer The scheduledTimer() method creates a timer according to the value of its
arguments and automatically adds it to an internal loop that will process it

when the time is up. The time is set in seconds with a TimeInterval value (a
typealias of Double), and we can declare the closure as a trailing closure to
Timers are objects created from the Timer class that perform an action after
simplify the code, as in the following example.
a specific period of time. There are two types of timers: repeating and non-
repeating. Repeating timers perform the action and then reschedule
Listing 4-41: Creating a non-repeating timer
themselves to do it again in an infinite loop. Non-repeating timers, on the

other hand, perform the action once and then invalidate themselves. The
import Foundation
Timer class includes the following properties and methods to create and
manage timers. print("Wait 5 seconds...")
Timer.scheduledTimer(withTimeInterval: 5.0, repeats: false) { (timer) in
print("The time is up")
isValid—This property returns a Boolean value that indicates if the }

timer can still be fired, or it was invalidated.
timeInterval—This property returns the time interval in seconds for The code in Listing 4-41 creates a non-repeating timer. It prints a message
repeating timers. and then initializes a timer with the scheduleTimer() method. The timer is set
tolerance—This property sets or returns a period of tolerance in to 5 seconds, non-repeating, and the closure just prints another message
seconds to provide the system with more flexibility. It is a value of on the console. When we execute the code, the first message appears on
type TimeInterval. The value by default is 0. the console and after 5 seconds the message "The time is up" is printed
scheduledTimer(withTimeInterval: TimeInterval, repeats: below.
Bool, block: Closure)—This type method returns repeating and The closure receives a reference to the Timer object that we can use to
non-repeating timers depending on the values of its arguments. The access the timer’s properties or invalidate it. It was not required in the last
withTimeInterval argument represents the seconds the timer must example, but it may be useful when working with repeating timers, as in
wait before performing the action, the repeats argument is a Boolean the following example.
value that determines if the timer is repeating (true) or non-repeating
(false), and the block argument is the closure to be execute when the Listing 4-42: Creating a repeating timer
time is up. 
import Foundation
fire()—This method fires the timer without considering the time
remaining. var counter = 0

invalidate()—This method invalidates the timer (stops the timer). func startTimer() {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timerref) in
report(timer: timerref)
}
} 4.3 Core Graphics
func report(timer: Timer) {
print("\(counter) times") 
counter += 1
if counter > 10 {
print("Finished") Core Graphics is an old framework programmed in the C language. It was
timer.invalidate()
} developed to provide a platform-independent two-dimensional drawing
} engine for Apple systems. The framework is composed of basic drawing
startTimer()

tools and its own data types. Due to its characteristics, instead of being
replaced, the framework was integrated with newer frameworks and,
In this code, we define two functions. The startTimer() function schedules therefore, it remains in use.
the timer, and the report() function is executed when the time is up. In this
last function, we count how many times the code is executed with the
counter variable and print a message on the console with the number. If the
value is greater than 10, we print the text "Finished" and invalidate the
timer, so the function is not executed anymore (repeating timers keep
running indefinitely until they are invalidated).

Data Types The structure defines initializers to create instances from values of
type Int, CGFloat, and Double.

zero—This type property returns a CGPoint structure with its values
What modern applications require the most from this old framework are set to zero.
its data types. In Swift, Core Graphics’ data types are implemented as x—This property sets or returns the structure's x coordinate.
structures, with their own initializers, properties, and methods. They can
store values that represent attributes of elements on the screen, such as
y—This property sets or returns the structure's y coordinate.
position or size. The following is a structure included in the framework to
specify floating-point values. There is also a more complex structure called CGRect that we can use to
define and work with rectangles. This data type includes the following
initializers and properties.
CGFloat—This structure is used to store values of type Double for
drawing purposes.
CGRect(origin: CGPoint, size: CGSize)—This initializer creates a
CGRect structure to store the origin and size of a rectangle. The origin
A more complex structure is CGSize, designed to store values that represent
dimensions. This data type includes the following initializer and properties. argument is a CGPoint structure with the coordinates of the rectangle's
origin, and the size argument is a CGSize structure with the rectangle's
CGSize(width: CGFloat, height: CGFloat)—This initializer creates width and height.
a CGSize structure with the values specified by the width and height CGRect(x: CGFloat, y: CGFloat, width: CGFloat, height:
arguments. The structure defines initializers to create instances from CGFloat)—This initializer creates a CGRect structure to store the
values of type Int, CGFloat, and Double. origin and size of a rectangle. The x and y arguments define the
zero—This type property returns a CGSize structure with its values set coordinates of the rectangle's origin, and the width and height
to zero. arguments its size. The structure defines initializers to create instances
from Int, CGFloat, and Double values.
width—This property sets or returns the structure's width.
zero—This type property returns a CGRect structure with its values set
height—This property sets or returns the structure's height.
to zero.
Another structure defined by the framework is CGPoint, which is used to origin—This property sets or returns a CGPoint structure with the
define points in a two-dimensional coordinate system. It includes the coordinates of the rectangle's origin.
following initializer and properties. size—This property sets or returns a CGSize structure with the
rectangle's width and height.
CGPoint(x: CGFloat, y: CGFloat)—This initializer creates a CGPoint
structure with the coordinates specified by the x and y arguments.
midX—This property returns the value of the rectangle’s x coordinate Listing 4-45: Accessing the structures inside a CGRect structure
located at the horizontal center of the rectangle. 
import CoreGraphics
midY—This property returns the value of the rectangle’s y coordinate
located at the vertical center of the rectangle. var myrect = CGRect(x: 30, y: 20, width: 100, height: 200)

var mypoint = myrect.origin


The structures provided by Core Graphics are declared and initialized as var mysize = myrect.size
any other structures in Swift, but we must import the Core Graphics print("The origin is at \(mypoint.x) and \(mypoint.y)")
print("The size is \(mysize.width) by \(mysize.height)")
framework first for the types to be recognized. 

Listing 4-43: Initializing Core Graphics' structures When we don't have initial values for the coordinates or the size, we can

use the zero type property to create a structure with all the values
import CoreGraphics initialized to 0.
var myfloat: CGFloat = 35
var mysize: CGSize = CGSize(width: 250, height: 250) Listing 4-46: Assigning empty structures to a CGRect variable
var mypoint: CGPoint = CGPoint(x: 20, y: 50) 
var myrect: CGRect = CGRect(origin: mypoint, size: mysize)
 import CoreGraphics

var myrect = CGRect.zero


The CGSize and CGPoint structures may be initialized with their member
print("The origin is at \(myrect.origin.x) and \(myrect.origin.y)")
initializers, but the CGRect structure provides an additional initializer to
print("The size is \(myrect.size.width) by \(myrect.size.height)")
create the instance from the values of its internal structures. 

Listing 4-44: Using the CGRect convenience initializer The myrect variable of Listing 4-46 is a CGRect structure with all its properties
 initialized with the value 0. Assigning the value of the zero property to a
import CoreGraphics variable is the same as using the initializer CGRect(x: 0, y: 0, width: 0, height: 0).
var myrect = CGRect(x: 30, y: 20, width: 100, height: 200) The CGRect structure also includes properties to calculate values from its
coordinates and size. For example, the midX and midY properties return the
print("The origin is at \(myrect.origin.x) and \(myrect.origin.y)")
print("The size is \(myrect.size.width) by \(myrect.size.height)") coordinates at the center of each side.

Listing 4-47: Calculating the coordinate at the center of the rectangle
The origin and size properties of a CGRect value are CGPoint and CGSize 
structures, respectively, so they can be copied into other variables or import CoreGraphics
properties as any other values. var rect = CGRect(x: 0, y: 0, width: 100, height: 100)

print("The horizontal center is \(rect.midX)") // 50.0


 CHAPTER 5 - UIKIT FRAMEWORK
IMPORTANT: In this chapter, we have explored the part of Core
Graphics used by modern frameworks to build user interfaces, but
the framework is extensive and includes tools for the creation of
custom graphics. For more information, visit our website and follow
the links for this chapter.
5.1 UIKit Application
 

UIKit (User Interface Kit) is the framework provided by Apple to define the When the user taps on the icon to run the application, the first step
elements of the graphic interface. From text to buttons, all the standard performed by the system is to create an object of a class called UIApplication
elements that users interact with on the screen to insert, select and (the prefix UI stands for UIKit). This object starts a loop to keep the
process information are defined by the classes in this framework. Although application running, it checks for events generated by the user or the
its primary function is to create the user interface, the framework also system, reports changes in the state of the app, and provides access to the
includes several classes to create the objects the application needs to work windows and the user interface.
and to connect with the rest of the system. The UIApplication object is automatically created as soon as our app is
launched and works along with the system to keep the application
Figure 5-1: App's ecosystem responsive. There is nothing we need to do to set up this object, but there
are times when we must access the object to configure the app or respond
to changes reported by the system. For this purpose, the class includes the
following type property.

shared—This type property returns a reference to the instance of the


UIApplication class created for our application.

The UIApplication object designates a delegate and calls some methods on it


that we can modify to perform custom tasks. The class includes the
following property to access this delegate.

delegate—This property returns a reference to the object assigned
as the delegate of the UIApplication object.

Devices like Mac computers and iPads can work with multiple instances of
an app (multiple windows). In UIKit, these instances are called Scenes. Each
copy of the app is a Scene, and the Scenes are associated to Scene sessions
for configuration. The UIApplication class includes the following properties to
manage the windows, the Scenes, and the Scene sessions of our
application.

learn, practice, and test code, but cannot simulate a system such as a
supportsMultipleScenes—This property returns a Boolean to mobile device, and therefore, UIKit classes only return values by
indicate if the application supports multiple Scenes (multiple default. Later in this chapter, we will learn how to create real
windows). applications with the Xcode main interface.
connectedScenes—This property returns a set with references to
the Scenes that are currently connected to the application.
openSessions—This property returns a set with references to the
Scene sessions currently active or archived by the system.
windows—This property returns an array of UIWindow objects
representing the visible and hidden windows managed by the app.

The following example illustrates how to access the UIApplication object and
how to read its properties.

Listing 5-1: Accessing the UIApplication object



import UIKit

let app = UIApplication.shared


if app.supportsMultipleScenes {
print("Multiple Windows App")
} else {
print("Single Window App") // "Single Window App"
}

In the code of Listing 5-1, we first import UIKit to have access to all the
tools defined in this framework and then read the shared property to get a
reference to the UIApplication object created for our app. With this
reference, we check whether the application allows the user to open
multiple windows or not by reading the supportsMultipleScenes property.

Do It Yourself: The examples in this section of the chapter were


designed for testing on Playground. Playground was developed to
Device
The implementation of this class is simple. We get an instance of the object

that represents the current device and then read its properties, as shown
next.
The UIKit framework also includes classes to manage the device in which
the app is running and its screen. For the device, UIKit defines the UIDevice
Listing 5-2: Getting information from the device
class. The following is the type property that returns the UIDevice object

representing the current device.
import UIKit

current—This type property returns the instance of the UIDevice class let current = UIDevice.current
let deviceName = current.systemName
that represents the device in which the app is currently running. let deviceVersion = current.systemVersion

print("\(deviceName) \(deviceVersion)") // "iPadOS 15.0"


The following are some of the properties and methods provided by the 
UIDevice class to read the device's configuration and activate or deactivate
some of its features. The device's screen is represented by an object of the UIScreen class. This
class provides information about the main screen and external screens
systemName—This property returns a string with the name of the connected to the device. The following are some of its properties.
operative system.
main—This type property returns the UIScreen object that manages
systemVersion—This property returns a string with the version of
the main screen.
the operative system.
bounds—This property returns a CGRect value with the dimensions of
orientation—This property returns a value that determines the
the screen expressed in points. The values vary according to the
current orientation of the device. It is an enumeration of type
device’s orientation.
UIDeviceOrientation with the values unknown, portrait, portraitUpsideDown,
landscapeLeft, landscapeRight, faceUp, and faceDown. For accuracy, the
nativeBounds—This property returns a CGRect value with the
property requires the accelerometer to be enabled. dimensions of the screen expressed in pixels. The values are always
returned considering the portrait orientation.
beginGeneratingDeviceOrientationNotifications()—This
method enables the accelerometer and begins delivering notifications scale—This property returns a CGFloat value representing the scale of
to communicate changes in the orientation. the screen. This is a value that translates between points and real
pixels on the screen, as we will see next.
endGeneratingDeviceOrientationNotifications()—This method
tells the system that the accelerometer is no longer required and brightness—This property sets or returns a CGFloat value that
stops the delivery of notifications. determines the brightness of the screen. It takes values from 0.0 to

1.0. iPad 5th/6th/7th/8th 768 x 1024 points 1536 x 2048 pixels 2x


scale
The screen of a device is composed of a grid of hundreds of dots called iPad Pro 9.7in/Mini 4th/5th 768 x 1024 points 1536 x 2048 pixels
pixels, ordered in rows and columns. The number of pixels varies from one 2x scale
device to another. To compensate for the disparities between devices,
Apple adopted the concept of points (sometimes called logical pixels). The The difference between points and pixels represents the scale. iPhones 3
goal was to have a unit of measurement that is independent of the device and older had a scale of 1 (1 point represents 1 pixel), but modern iPhones
and the density of the pixels on the screen. A point occupies a square of have a scale up to 3 (1 point represents a square of 3 pixels), while iPads
one or more pixels, depending on the device, but the developer does not have a scale of 2 (1 point represents a square of 2 pixels). Working with
need to know how many, it is all managed by the system. points instead of pixels makes it easy for developers to create graphic
At the time of writing, points in Apple mobile devices may represent a interfaces that adapt to every device.
square of up to three pixels, depending on the device and the technology. To determine the location of each point, Apple uses a coordinate system.
The following is the list of devices available in the market that can run the The system counts the columns and rows from the top-left corner to the
latest operative system, along with the screen resolutions they support. bottom-right corner, as illustrated next.

iPhone 6S/7/8/SE2 375 x 667 points 750 x 1334 pixels 2x scale Figure 5-2: Coordinate system
iPhone 6S/7/8 Plus 414 x 736 points 1080 x 1920 pixels 3x scale
iPhone X/XS/11 Pro 375 x 812 points 1125 x 2436 pixels 3x scale
iPhone XR/11 414 x 896 points 828 x 1792 pixels 3x scale
iPhone XS Max/11 Pro Max 414 x 896 points 1242 x 2688 pixels 3x
scale
iPhone 12/13 Mini 375 x 812 points 1080 x 2340 pixels 3x scale
iPhone 12/13 12/13 Pro 390 x 844 points 1170 x 2532 pixels 3x
scale
iPhone 12/13 Pro Max 428 x 926 points 1284 x 2778 pixels 3x
scale

iPad Pro 12.9in 1024 x 1366 points 2048 x 2732 pixels 2x scale
iPad Pro 11in 834 x 1194 points 1668 x 2388 pixels 2x scale
The properties bound, nativeBounds, and scale included in the UIScreen class
iPad Pro 10.5/ Air 3rd 834 x 1112 points 1668 x 2224 pixels 2x
return the values corresponding to the screen of the device where the app
scale
is running. The bound and nativeBounds properties contain CGRect structures,
iPad Air 4th 820 x 1180 points 1640 x 2360 pixels 2x scale
while scale is just a CGFloat value.
iPad 7th 810 x 1080 points 1620 x 2160 pixels 2x scale
Listing 5-3: Getting information from the screen Windows


import UIKit

let screen = UIScreen.main The window is a space on the screen where the elements of the user
let pointsWidth = screen.bounds.size.width
interface are laid out. UIKit includes the UIWindow class to create the object
let pointsHeight = screen.bounds.size.height that manages the windows in our app. When the system creates a new
print("Width: \(pointsWidth) x Height: \(pointsHeight)") instance of our app (Scene), either because the app was just launched or
let pixelsWidth = screen.nativeBounds.size.width because the user requested a new window, a UIWindow object is created
let pixelsHeight = screen.nativeBounds.size.height and assigned to the Scene.
print("Width: \(pixelsWidth) x Height: \(pixelsHeight)")
A UIWindow object defines the space occupied by the user interface, but the
print("Scale: \(screen.scale)") interface is created with objects called Views, as we will see next.

Therefore, once the UIWindow object is initialized, we must tell the system
which views are going to be shown first. The following is the property
The native resolution in pixels is always returned considering a portrait
included by the class for this purpose.
orientation, but the size in points is returned according to the current
orientation. For example, the iPhone X’s screen is 375 points wide and 812
rootViewController—This property sets or returns a reference to
points tall in portrait, but 812 points wide and 375 points tall in landscape.
the object that controls the app’s initial view.

There could be multiple windows available (one per Scene), and some may
be hidden. The following are some of the properties and methods provided
by the class to manage these situations.

isKeyWindow—This property returns a Boolean value that


determines whether the window is the key window (the window
currently in charge of receiving input from the user).
windowScene—This property sets or returns a reference of the
Scene that contains the window. It is of type UIWindowScene.
makeKeyAndVisible()—This method positions the window in front
of any other windows that may exist for the app and makes it visible.

Views The following are the most useful initializer, properties, and methods
available.

UIView(frame: CGRect)—This initializer creates a UIView object in
The window is the space where the graphics are displayed, but it does not
the position and size determined by the frame argument.
generate any visible content. The user’s interface is built inside the window
from similar containers called Views. These views are rectangular areas of frame—This property sets or returns a CGRect value that determines
custom size, designed to display graphics on the screen. Some views are the position and size of the rectangular area occupied by the view.
used just as containers while others are improved to present graphic tools, bounds—This property sets or returns a CGRect value that
such as buttons and switches, and graphic content, such as images and determines the position and size of the rectangular area occupied by
text. The views are organized in a hierarchy, one inside another, with a view
the view inside its own frame.
of the size of the window as the root (usually called main view or container
view), as illustrated next. backgroundColor—This property sets or returns an object that
determines the color of the view’s background. The value is an object
Figure 5-3: Views hierarchy of the UIColor class.
alpha—This property sets or returns a CGFloat value that determines
the view’s alpha level (the level of transparency). The property takes
values from 0.0 (transparent) to 1.0 (opaque).
isHidden—This property sets or returns a Boolean value that
determines whether the view is visible (false) or hidden (true).
isOpaque—This property sets or returns a Boolean value that
determines whether the view is opaque or not.
 contentMode—This property sets or returns a value that
determines the mode used by the view to lay out its content when its
No matter the purpose of the view (to be a container or to present size changes (frequently used with images). It is an enumeration
content), they are all created from objects of the UIView class. This is a basic called ContentMode included in the UIView class. The values available are
class that is only capable of creating and managing the rectangular area scaleToFill, scaleAspectFit, scaleAspectFill, redraw, center, top, bottom, left, right,
occupied by the view, but subclasses of this class are defined to add
topLeft, topRight, bottomLeft, and bottomRight.
functionality and present any type of content we want. Although the
objects created directly from the UIView class are very limited, they have an clipsToBounds—This property sets or returns a Boolean value that
extensive list of properties and methods to configure and draw their views. determines whether the content of the view is confined to the view’s
bounds or not.
isUserInteractionEnabled—This property sets or returns a Boolean The initializer of the UIView class takes a parameter called frame of type
value that determines whether the view responds to the user’s CGRect to establish the position and size of the view. The view created by

interaction (e.g., a tap of the finger). the code in Listing 5-4 is positioned at the window's coordinates 0, 0 and
has a size of 375 by 667 points.
isMultipleTouchEnabled—This property sets or returns a Boolean
value that determines if the view can handle multiple touch events.
Do It Yourself: Replace the code in your Playground file with the
superview—This property returns a reference to the UIVIew object code in Listing 5-4. In the Results Side Bar, click on the Quick Look
that is the container of the view. If the view is not inside another view, button corresponding to the line that creates the container object to
the value returned is nil. see a representation of the view. At this moment, you will only be
subviews—This property returns an array containing references to able to see a gray rectangle.
the UIView objects that are inside the view (subviews).
tag—This property sets or returns an integer value that identifies the The background color of the UIView object is set as transparent by default.
view. If we want to change the color, we must assign a new value to the
backgroundColor property of the view. This value must be provided as an
viewWithTag(Int)—This method searches for a subview of the view object of the UIColor class. This class includes some initializers and
with the tag specified by the argument. The value returned is an properties to create the objects. The following are the most frequently
optional containing a UIView object or nil in case no view with that tag used.
was found.
UIColor(red: CGFloat, green: CGFloat, blue: CGFloat, alpha:
The window is positioned inside the screen, while the views are positioned CGFloat)—This initializer creates a UIColor object with a color set by
inside the window and inside one another, generating a hierarchical the values of its arguments. It takes values from 0.0 to 1.0. The red
structure (see Figure 5-3). As we already mentioned, the position of these argument defines the level of red, green the level of green, blue the
elements is determined by a coordinate system. For instance, the next
level of blue, and alpha defines the alpha level (transparency).
example creates a view of 375 points by 667 points, akin to the main view
of a small iPhone. UIColor(patternImage: UIImage)—This initializer creates a UIColor
object with a color defined as the image provided by the
Listing 5-4: Creating a view patternImage argument. The image will be replicated as many times
 as necessary to fill the area to which the color is assigned.
import UIKit

var mainframe = CGRect(x: 0, y: 0, width: 375, height: 667)


The UIColor class also offers an extensive list of type properties that return a
let container = UIView(frame: mainframe) UIColor object with a predefined color. The following are the properties

Apple recommends using to define standard colors because they adapt the

color to the appearance (light or dark): systemBlue, systemBrown, systemGreen, the type property systemRed, although with the difference that this color
systemIndigo, systemOrange, systemPink, systemPurple, systemRed, systemTeal, adapts to the appearance (light or dark).
systemYellow, systemCyan, systemMint, systemGray, systemGray2, systemGray3,
systemGray4, systemGray5, and systemGray6. There are also properties that Listing 5-6: Assigning an adaptable color to the view
return predefined colors for labels: label, secondaryLabel, tertiaryLabel, and 
quaternaryLabel. And properties with predefined fill colors: systemFill, import UIKit
secondarySystemFill, tertiarySystemFill and quaternarySystemFill. A few properties
let container = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 667))
with predefined colors for backgrounds: systemBackground, let color = UIColor.systemRed
secondarySystemBackground, and tertiarySystemBackground. Other properties that container.backgroundColor = color

return colors for a variety of interface elements, such as placeholderText,
separator, and link. A property called clear to assign a transparent color.
Do It Yourself: Replace the code in your Playground file with the
Another called tintColor that represents the app's global tint color. And
code in Listing 5-6. In the Result Side Bar, click on the Show Result
finally, some properties with fixed colors that do not adapt to the
appearance: black, blue, brown, cyan, darkGray, gray, green, lightGray, magenta, button corresponding to the line that assigns the new color to the
backgroundColor property of the view. You should see a rectangular red
orange, purple, red, white, and yellow.
UIColor objects are assigned to the views and most of the elements in the view within the Editor Area.
user interface to define their colors. They are created from the initializers
or type properties listed above and then assigned to the corresponding IMPORTANT: The UIColor initializer uses values from 0.0 to 1.0 to
properties, as in the following example. determine the levels of red, green, and blue that define the
composition of the color, but RGB colors are usually determined with
Listing 5-5: Assigning a background color to a view integer values from 0 to 255. If you prefer to work with these values,
 you can convert them dividing the values by 255.0. For example, if
import UIKit the level of red is 190, you can get the value for the initializer with
the formula 190.0 / 255.0.
let container = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 667))

var color = UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0) The user’s interface is built by adding views to parent views that work as
container.backgroundColor = color
containers, creating the hierarchical structure introduced in Figure 5-3. The

container view is called the superview and the views inside are called
The color for the UIColor object created in Listing 5-5 is defined by the subviews. When a view is added to a superview, the object is stored in the
initializer's arguments. The system used in this case is called RGB. The color subviews property of the superview. This is a property containing an array of

is constructed by adding the values of every component: Red, Green, and views. To add and manage the views inside this array, the UIView class offers
Blue. In this example, the red argument was set to 1.0 while the rest of the the following methods.
colors were set to 0.0, defining a pure red. A similar color is returned by
addSubview(UIView)—This method adds a view at the end of the container.backgroundColor = UIColor.systemRed

subviews array. let subview1 = UIView(frame: CGRect(x: 20, y: 20, width: 335, height: 300))
subview1.backgroundColor = UIColor.systemGray4
insertSubview(UIView, at: Int)—This method inserts a view in the
subviews array at the index indicated by the at argument. let subview2 = UIView(frame: CGRect(x: 20, y: 347, width: 335, height: 300))
subview2.backgroundColor = UIColor.systemGray4
insertSubview(UIView, aboveSubview: UIView)—This method
container.addSubview(subview1)
inserts a view in the subviews array above the subview indicated by the container.addSubview(subview2)
aboveSubview argument. 

insertSubview(UIView, belowSubview: UIView)—This method


After creating the container view, the code in Listing 5-7 creates two more
inserts a view in the subviews array below the subview indicated by the
views and adds them as subviews with the addSubview() method. The subviews
belowSubview argument. property of container now has two elements: the subview1 and subview2 views.
bringSubviewToFront(UIView)—This method moves the subview These subviews are of different color, creating two smaller rectangles of
specified by the argument to the end of the subviews array (to the 335 by 300 points inside the main view, as shown in Figure 5-4.
front).
Figure 5-4: Views on Playground
sendSubviewToBack(UIView)—This method moves the subview
specified by the argument to the beginning of the subviews array (to the
back).
removeFromSuperview()—This method removes a subview from
its superview.

The subviews are shown on the screen according to their position on the
subviews array. The subviews with a lower index are drawn first, so the views
with a higher index are shown at the top. This is why the method most 
frequently used to add a view to another view is addSubview(). This method
adds the view to the end of the subviews array, effectively drawing the view Every subview is attached to its superview and positioned on the screen
at the front. according to its coordinate system. When we set the values of the x and y
parameters in the UIView initializer for every view, we must consider the
Listing 5-7: Adding subviews to a container view coordinates of their superviews, not the screen or other container views
 up the chain. For example, the position of the subview1 view inside the
import UIKit container view is 20, 20, but its own coordinate system starts again from 0, 0
at its top-left corner. The same happens with subview2. Any views added to
let container = UIView(frame: CGRect(x: 0, y: 0, width: 375, height: 667))

these subviews will have a position according to the coordinates of their UIView Subclasses
superviews, not the coordinates of the container view or the window. Figure

5-5 illustrates these views and their coordinate systems.
The views created from the UIView class are good as containers and
Figure 5-5: Subviews and their coordinates
organizers but do not present any content. The content is generated by
subclasses of UIView. These subclasses overwrite some UIView methods and
provide their own properties and methods to draw graphics inside the view
or respond to user interaction. Considering how difficult and time
consuming it is to create every single element of the interface from
scratch, UIKit provides ready to use subclasses of UIView for the creation of
standard elements. From labels and buttons to images and tables, there is
a UIView subclass to present on the screen everything our app needs. Figure
5-6 shows a scheme with the UIView class and some of its most important
subclasses.

 Figure 5-6: UIView subclasses

These values are provided by the frame and bounds properties of the UIView
objects that represent each view. For example, the x and y values of the
frame property of subview1 are 20, 20, but the x and y values of the bounds
property of subview1 are 0, 0, because the bounds property refers to the
view’s internal frame and its own coordinate system.

Some classes that add graphic content, such as UILabel or UIImageView,


inherit directly from UIView, while classes that provide more complex
functionality, such as UIButton or UITableView, inherit from intermediate
subclasses. The two intermediate subclasses depicted in Figure 5-6, Scenes
UIControl and UIScrollView, provide common functionality for their own

subclasses. UIControl adds to UIView the capacity to respond to user
interaction and report events, and UIScrollView adds the possibility to create
A UIApplication object runs the application, a UIDevice object provides
views with scrollable content.
information about the device, a UIScreen object describes and configures
the screen, and UIWindow and UIView objects define the interface, but this is
not enough to control the application. In some devices, multiple instances
of the same application can run at the same time. For example, we may
have two instances of the Text Editor on the screen processing two
different documents, or two instances of a browser loading two different
websites. To control these instances, the UIKit framework defines Scenes.
Scenes are what users call windows. Some devices, such as iPhones and
iPods Touch, can only work with one Scene at a time (one window), but
iPads and Mac computers can work with multiple Scenes (multiple
windows). UIKit defines the UIWindowScene class to control each Scene. The
following are some useful properties defined by this class.

windows—This property returns an array of UIWindow objects that


represent the windows managed by the Scene.
screen—This property returns a reference to the UIScreen object that
displays the content of the Scene.
interfaceOrientation—This property returns the interface
orientation. It is an enumeration of type UIInterfaceOrientation with the
values unknown, portrait, portraitUpsideDown, landscapeLeft, and landscapeRight.
The enumeration also includes the Boolean properties isLandscape and
isPortrait to quickly get the current orientation.

statusBarManager—This property returns a UIStatusBarManager


object with the configuration of the status bar. This object includes
properties like isStatusBarHidden (a Boolean value that determines

whether the status bar is hidden or not) and statusBarFrame (a CGRect


value with the position and size of the bar), among others.

A UIWindowScene object is automatically created per Scene and then


UIWindow objects are created and assigned to the Scenes to display the user
interface, as shown below.

Figure 5-7: Single Scene
No matter if the app is showing one Scene or several, the process to define
the content of each one of them is the same. Once the window is created
for the Scene, we must add all the views inside with the content we want
to show to the user. We will study this process next.

iPads and Mac computers allow multiple instances of our app to run at the
same time. When the user opens multiple windows with our app, the
system generates one Scene per window.

Figure 5-8: Multiple Scenes


5.2 Xcode Projects
 

Creating the app's interface by adding subviews to the main view from our Applications are built from several files and resources, including our own
code, as we did in previous examples, may be appropriate for very small codes, frameworks, images, databases, and more. Xcode organizes these
applications, but as the interface grows and new elements and elements into projects. An Xcode project comprises all the information and
functionality are introduced, the code becomes difficult to develop and resources necessary to create one application. The welcome window,
illustrated in Figure 1-2, presents a button called Create a new Xcode
maintain. This is especially true with mobile applications, where we not
project to initiate a project. When we click on this button, a new window
only have to define the initial interface but also adapt it to new conditions
presents the templates we can choose from to create our project.
produced by the rotation of the screen or the app running on different
devices. Xcode expedites our work by providing a set of graphic tools that Figure 5-9: Selecting the type of project we want to create
allow us to create and modify the views and the application. This toolset
includes an editor, a visual interface, resource managers, configuration
panels, and debugging tools, all integrated into a single working space.

The options are organized in several categories—Multiplatform, iOS,


macOS, watchOS, tvOS, DriverKit, and Other—representing each operative
system Apple has to offer. In this book, we are going to learn how to
develop applications for iPhones and iPads with UIKit, so we must select
the App template under the iOS category, as shown in Figure 5-9.
Once we select the type of project we want to build, the next step is to
provide information about the project. Figure 5-10, below, shows the
values required for the App template and the iOS system.

Figure 5-10: Project configuration window

screen. To develop applications with the UIKit framework, we must select


the Storyboard option. Finally, we must select the Swift language.

IMPORTANT: There are additional options at the bottom that we


can check to prepare our template to work with complementary
systems like Unit Testing and Core Data. For most projects, these
options are not required, but we will explore some of them in
following chapters (see Core Data in Chapter 15 and CloudKit in
Chapter 17).

As mentioned above, the Team's field shows a list of developer accounts


registered with Xcode. If we haven't yet inserted our developer account,
we will see the Add Account button instead. Pressing this button opens a
window to insert our Apple ID or create a new one.

Figure 5-11: Registering Apple account in Xcode


The Product Name is the name of the project. By default, this is the name
of our app and the folder where the project is stored, so we should write a
name that is appropriate for our application. Next is the Team's account.
This is the developer account created with our Apple ID at
developer.apple.com, or our company's account. If we haven't yet
registered our account with Xcode, we will see the Add Account button to
add it (we explain how to add an account to Xcode below). The next value
is the Organization Identifier. Xcode uses this value and the project’s name
to create a unique identifier for our app and therefore it is recommended

to declare it with an inverted domain, as we did in our example
(com.formasterminds). Using the inverted domain ensures that only our
Once the Apple ID is inserted, the account is configured to work with our
app will have that identifier (com.formasterminds.Test). Next, we must
copy of Xcode. With the account selected and all the information inserted
select the technology we are going to use to design the user interface.
in the form, we can now press the Next button to designate the folder
There are two options available: SwiftUI and Storyboard. SwiftUI is a
where our project is going to be stored. Xcode creates a folder for each
system that allows us to declare the interface from code, while Storyboard
project, so we only need to specify the destination folder where we are
is a graphic tool that produces a visual representation of the user interface
going to store all our projects and everything else is generated for us.
and allows us to modify it by just dragging and dropping elements on the
Do It Yourself: Open Xcode. In the welcome screen, click on the Tools
option Create a new Xcode project (Figure 1-2). You can also go to

the File menu at the top of the screen and select the options
New/Project. After this, you should see the templates window After the project is created, Xcode generates the files required by the
(Figure 5-9). Select the iOS tab, click on the App icon, and press Next. template and presents the main interface on the screen. Figure 5-12, next,
Now you should see the form of Figure 5-10. In Product Name, Insert shows what this interface looks like.
the name of your project. In the Team option, select your developer
account or press the Add Account button to register your account Figure 5-12: Xcode’s interface
with Xcode. In Organization Identifier insert the inverted domain of
your website or blog. Next, select the Storyboard interface and the
Swift language. Make sure that the rest of the options are disabled.
Finally, press Next and select a folder where to store your project.

IMPORTANT: Although it’s not mandatory, you should get your


own domain and website. Apple not only recommends the use of an
inverted domain to generate the Bundle Identifier, but at the time of
submitting your app to the App Store you will be asked to provide
the web page used for promotion and where the users should go for
support.

Like the Playground interface, the Xcode's main window is organized in


several areas. There is a toolbar at the top, an area to edit the files at the
center called Editor Area, and three removable panels on the sides and
bottom called Navigator Area, Debug and Console Area, and Utilities Area.

Toolbar—This is the area at the top with buttons to control the


appearance of the interface, and a display to show warnings, errors,
and the app's status. It provides buttons to run and stop the app
(Figure 5-12, number 1), a drop-down list to select the device or the
simulator where we want to run the app (Figure 5-12, number 2), a

button that opens a popup window with tools to create the user Running the App
interface (Figure 5-12, number 5), and two buttons to show or remove 
the Navigator Area and the Utilities Area (Figure 5-12, number 3 and
4). Xcode offers two ways to run our application: the simulator or a real
Navigator Area—This is a removable area that provides information device. The buttons to select these options, execute, and stop the app are
about the files that comprise the application and tools for debugging on the toolbar.
(identify and remove errors in the code). From here, we can select the
files to edit, create groups to organize those files, add resources, Figure 5-13: Buttons to run the app
check for errors, and more. In addition to the files, this area shows an
option at the top to configure the app (Figure 5-12, 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 Applications are run on a specific destination. This destination could be
cannot be removed, it includes buttons at the top to split the area different things, from real devices to windows or simulators, and have
different configurations, including settings like the region where the device
into multiple editors or panels, as we will see later (Figure 5-12,
is located, and the human language used by the app to display the
number 8).
information on the screen. Xcode allows us to define and configure the
Debug and Console Area—This is a removable area that splits into possible destinations for our app using an arrangement called Scheme.
two sections that can be shown or hidden by the buttons at the When a project is created, a Scheme is automatically generated for us with
bottom (Figure 5-12, number 7). The section on the left provides options that are according to the type of project and our local settings
information for debugging, while the section on the right is a console (region and language set on the computer). For example, the default
to display the results of the execution of our code, including warnings Scheme of a project for a mobile application includes several iOS
and errors. simulators and offers access to the devices currently connected to the
computer. The Scheme is selected from the button on the left of the
Utilities Area—This is a removable area that provides additional
Scheme section and the destination is selected from the button on the
information about the app and allows us to edit the configuration of
right. When the destination button is clicked, we can see a list of the
the interface and its elements. devices and simulators available.

Figure 5-14: Options available to run the app


During development, the most common destination is the simulator. The 


simulator is a program included in the Xcode’s package that can recreate a
specific device in a separate window. It is a practical tool because it is fast, IMPORTANT: Pressing the Play button on the toolbar runs the
and it can simulate almost every feature of a device. application on the simulator or a device, but we can also build the
To test the app, all we need to do is to select the simulator from the app without running it. This is useful for checking your code for
Scheme and then click the Play button. Figure 5-15, below, shows the errors. You can build the app by pressing the Command + B keys on
simulator configured as an iPhone, running the app created by the App your keyboard or by selecting the Build option in the Product menu
template (an empty screen). at the top of the screen.

Figure 5-15: iPhone simulator running the app created by the App template

5.3 Templates MVC


 

Templates include files with resources and sample code that we can use to
start building our application, but to take advantage of these files we need
to understand first how Xcode suggests applications should be built and
the tools it provides for this purpose.
Xcode proposes an architectural paradigm that divides the application in This structure is created from several objects. The interface is created from
three interconnected parts, the Model, the View, and the Controller (called the UIView objects introduced before, which are connected to controller
MVC for short). Every part of the code performs a specific task and objects called View Controllers, and these objects are connected to the
communicates to the other parts only the information strictly necessary. objects that manage the data. Although we can define all the objects from
Figure 5-16 illustrates the elements involved. scratch, the files created by the App template provide the definition of
some of the classes we need to create these objects and other resources.
Figure 5-16: MVC architecture
AppDelegate.swift—This file defines a class called AppDelegate to
create an object that is going to work as the delegate of the
object. Every time the state of the application changes,
UIApplication
methods of this delegate are called to report the change.
SceneDelegate.swift—This file defines a class called SceneDelegate to
create an object that is going to work as delegate of the Scenes
(windows). When a Scene is created, an object of this class is assigned
to it. Every time the state of the Scene changes, methods of this
delegate are called to report the change to our code.
ViewController.swift—This file defines a class called ViewController to
control the only view added to the Storyboard by the App template.
Main.storyboard—This file defines our app’s Storyboard; a visual

editor to create the user interface. The Storyboard provided by the
The Model is the data the app is going to process, the View is the user App template includes only one view, but we can add more as we will
interface (window and views), and the Controller is the object in charge of see later.
receiving input from the views, processing the data, and updating the
Assets.xcassets—This is a folder called Assets Catalog used to
interface. The objects managing the data and the views are not connected
with each other, they send the information to the controller and this code organize the app’s resources.
decides what to do with it. This allows reusability. We can use the same LaunchScreen.storyboard—This is a Storyboard with a single view
code to control different views or replace the user’s interface entirely used to define the app’s launch screen (the screen shown by the
without having to change a line of code in the rest of the program.
system while the app is starting up).

Info.plist—This is a file with structured text to define the app’s basic Application Delegate
configuration. 

Do It Yourself: Open the Navigator Area (the panel on the left). You The first file on the list is called AppDelegate.swift and contains a class
should see the list of files created by the App template. If the files called AppDelegate. As we already mentioned, when the app is launched, the
are not visible, click on the Project Navigator button at the top-left system creates an object of the UIApplication class to provide the basic
corner of the Navigator Area. functionality necessary for the app to process information and respond to
user interaction. The object created from the AppDelegate class is assigned as
the delegate of the UIApplication object. Every time the system needs to
report a change in the state of the application to our code, it calls methods
on this delegate. For this purpose, the AppDelegate class conforms to the
UIApplicationDelegate protocol. The following are the methods defined by the
protocol to initialize the app and configure the Scenes.

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 were already
instantiated, and the app is ready to work.
application(UIApplication, configurationForConnecting:
UISceneSession, options: UIScene.ConnectionOptions)—This
method is called when a new Scene (window) is requested by the
system or the user. The method must return a UISceneConfiguration
object with the Scene’s configuration.
application(UIApplication, didDiscardSceneSessions: Set)—
This method is called when the user discards a Scene (closes a
window). The didDiscardSceneSessions argument is a set with
references to the UISceneSession objects representing the Scenes'
sessions.
As soon as the app is launched, the UIApplication object calls the The other two methods defined in the UIApplicationDelegate protocol are
application(UIApplication, didFinishLaunchingWithOptions:) method. In this method, called when a Scene is created or discarded. When the system or the user
we can write all the code we need to prepare our application, such as generates a new window, the application(UIApplication, configurationForConnecting:,
declaring initial values, opening a database, checking the current settings, options:) method is called in the delegate. From this method, we must return

and more, as shown next. the Scene's configuration, which is defined with an object of the
UISceneConfiguration class. The class includes the following initializer.

Listing 5-8: Defining a property in the app's delegate object


UISceneConfiguration(name: String?, sessionRole:

UISceneSession.Role)—This initializer returns a UISceneConfiguration
import UIKit
object configured with the values provided by the arguments. The
@main name argument is a string with the name that identifies the
class AppDelegate: UIResponder, UIApplicationDelegate {
var basicSalary: Double! configuration. The sessionRole argument is a constant from a
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
structure called Role that specifies the role of the Scene. The constant
[UIApplication.LaunchOptionsKey: Any]?) -> Bool { available to create Scenes for iPhones, iPads and Mac computers is
basicSalary = 30000.0
called windowApplication (windows on the main screen).

return true
This initializer provides the name of the configuration and the role of the
}
func application(_ application: UIApplication, configurationForConnecting Scene, but we also must declare the name of the class that is going to be
connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> assigned as the Scene's delegate. For this purpose, the UISceneConfiguration
UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: class includes the following property.
connectingSceneSession.role)
}
}
delegateClass—This property sets or returns the class that is going
 to be used to create the Scene's delegate (the object the system is
going to use to report the state of the Scene).
This example adds a property called basicSalary to the AppDelegate class. When
the application(UIApplication, didFinishLaunchingWithOptions:) method is called, we There are two ways to define the delegate class. We can use the initializer
initialize this property with the value 30000.0. This is a simple but practical to declare the name of the configuration and the role, and then assign the
example that illustrates how we can add properties to the application name of the class used to create the Scene's delegate to the delegateClass
delegate and initialize them when the app is launched. After the property property, or we can define this value in a file called Info.plist. The App
is implemented in the delegate, we can access it from any part of our code template implements this last approach. It defines the name of the
through the delegate property of the UIApplication object, as we will see later. configuration and the delegate class in the Info.plist file, as shown below,
and then calls the initializer with that name.

Scene Delegate
Figure 5-17: Scene definition in the info.plist file

Another file generated by the App template is called SceneDelegate.swift


and it defines the delegate for the Scenes (windows). The UIWindowScene
object calls methods on this delegate to report changes in the Scene. From
these methods, we can perform custom tasks every time a new Scene is
created or the state of a Scene changes. The methods are defined by the
UIWindowSceneDelegate protocol, which inherits from the UISceneDelegate
 protocol. The following are the most useful.

After a Scene is requested by the system or the user, the scene(UIScene, willConnectTo: UISceneSession, options:
application(UIApplication, configurationForConnecting: UISceneSession, options:
UIScene.ConnectionOptions)—This method is called when a new
UIScene.ConnectionOptions) method is called and a UISceneConfiguration object is
Scene is created.
created using the configuration defined in the Info.plist file with the name
"Default Configuration" and the role received by the method sceneDidDisconnect(UIScene)—This method is called when a
(connectingSceneSession.role). Scene was removed.
sceneDidBecomeActive(UIScene)—This method is called when a
IMPORTANT: The configuration defined by the App template is Scene becomes active and therefore it is responding to user events.
enough for most applications, so all we need to do is to leave the
sceneWillResignActive(UIScene)—This method is called when a
method as it is declared by the template and the Scenes will be
properly configured. We will learn more about the info.plist file later, Scene is about to be moved to the background and stop responding to
and how to work with multiple Scenes in Chapter 22. user events.
sceneWillEnterForeground(UIScene)—This method is called
when a Scene is about to become visible and respond to user events.
sceneDidEnterBackground(UIScene)—This method is called
when a Scene is no longer visible and does not respond to user events
anymore.

In addition to these methods, the UIWindowSceneDelegate protocol also


defines a property we must implement to store a reference to the window
assigned to the Scene. Storyboard

window—This property sets or returns a reference to the UIWindow
object assigned to the Scene. The user interface is built with views inside a window. One view, usually
called main view or container view, is responsible for organizing all the
When a Scene is created, the system calls the scene(UIScene, willConnectTo:, graphic elements on the screen. Several main views put together simulate
options:) method on the Scene's delegate, creates a UIWindow object to virtual screens. This is how the app manages multiple screens and expands
represent the Scene’s window, and assigns it to the window property. the interface in mobile devices. The main views replace one another inside
Because this method may be called not only when a new Scene is created the same window to give the impression of having more space than the
but also in other circumstances, the code generated by the App template window can offer.
includes a guard statement to make sure that the UIScene object received by
the method is an object of the UIWindowScene class. If the casting performs Figure 5-18: Multiple views to expand the interface
correctly, then we can proceed to execute any custom task we need to
initialize the Scene, as shown next.

Listing 5-9: Initializing a Scene



import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {


var window: UIWindow? 
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions:
UIScene.ConnectionOptions) { Creating and organizing these views and their content from code could
guard let _ = (scene as? UIWindowScene) else { return }
demand hours of work. This is the reason why Xcode introduces a graphic
print("The Scene is ready") tool called Storyboard. The Storyboard produces a result like Figure 5-18. It
} allows us to visually organize the main views and their content. Every
}
 component of the interface is displayed in the Editor Area, and we can drag
and connect everything without writing a single line of code. All the
information for the views created in the Storyboard is stored in an XML file.
When the app is executed, this XML code is interpreted, the real objects
are instantiated, and the views are drawn on the screen.

The files that store the information for the Storyboard have the storyboard 19, number 2) and a small bar at the top with options to configure the
extension. As we already mentioned, the App template includes the scene and the view controller (Figure 5-19, number 1). Clicking on the main
Main.storyboard file with a Storyboard for our app. Figure 5-19 shows area selects the main view and clicking on the bar selects the scene. The
what we see on the Editor Area when we click on that file (the shape and view controller may also be selected from the buttons shown on the bar
size of the view vary depending on the selected device, as we will see when we click on it (see Figure 5-20, below). The button on the left
later). represents the controller object responsible for the view, the button in the
middle provides options to configure the element designated as first
Figure 5-19: Scene in the Storyboard responder (introduced later), and the button on the right allows us to
connect scenes together (studied in Chapter 9).

Figure 5-20: Buttons to configure the scene

The initial shape of the scenes resembles an iPhone, but we can change it
anytime we want from the toolbar at the bottom of the Editor Area.

Figure 5-21: Editor Area's toolbar

All the buttons on this bar help us configure the Storyboard and its
content, as described next.

Document Outline (1)—This button opens a panel on the left side



of the Editor Area with a list of the scenes available in the Storyboard,
the main views, content, and other components. From this list, we can
The box shown in Figure 5-19 is called scene (not to be confused with the
select the elements of the interface, edit their names, remove them,
instances of our app which are also called Scenes, as we have seen in the
or change their order.
previous section). The Storyboard uses these boxes to represent main
views. Each scene contains a view where the interface is created (Figure 5-
Appearance (2)—These four buttons configure the Storyboard
appearance. The first button opens a window to modify accessibility
parameters, the second button toggles the appearance between Light
and Dark, the third button rotates the scenes (portrait or landscape),
and the fourth button simulates a split view (available on iPads).
Device Configuration (3)—This button opens a window with the
list of devices we can select to change the appearance of the scenes.
Zoom (4)—These buttons allow us to zoom the Storyboard in and
out.
Auto Layout (5)—These buttons open popup windows to create, 
configure, and resolve issues with constraints (we will study
constraints and Auto Layout in Chapter 6). The values in the panels of Figure 5-22 correspond to the same properties
introduced before for UIView objects. For example, if we change the value
When we select an option from these buttons, all the scenes in the
of the Background field in the Attributes inspector panel (Figure 5-22, left),
Storyboard change to represent the new configuration, allowing us to
the selected value will be assigned to the backgroundColor property of the
design the interface for every device available.
UIView object created to represent the main view. The same happens with
Every time an element in the Storyboard is selected, the Utilities Area (the
removable panel on the right) shows a series of panels for configuration. the rest of the properties displayed on the panels. All the values defined in
The panels are selected from the buttons at the top of the area. The most these panels will be assigned to the corresponding properties of the object
useful are the Attributes Inspector panel and the Size Inspector panel. created later to represent the element.
From the Attributes Inspector panel, we can modify properties related to
the attributes of the selected element, and from the Size Inspector panel Do It Yourself: Click on the Main.storyboard file in the Navigator
we can change its size and position. Figure 5-22 shows how to open these Area to see its content in the Editor Area. You should see something
panels and some of the properties they include when the main view is like Figure 5-19. Click on the main view to see the configuration
selected. options in the Utilities Area. Select a new color in the Background
option to change the color of the view (Figure 5-22, left).
Figure 5-22: Attributes Inspector panel and Size Inspector panel

View Controllers The subclass that controls the scene is specified by the first option (Figure
5-23, number 1). We can write the name of the class or select it from the

list. For instance, the App template includes the ViewController.swift file
with a class called ViewController that inherits from the UIViewController class
Each scene in the Storyboard is responsible for showing a view and its and is in charge of controlling the only scene in the Storyboard. If we select
content on the screen. To control the scenes and perform any standard this scene, we will see that the ViewController class was already assign to this
function they require, the UIKit framework defines the UIViewController class. scene.
This is a basic class with properties and methods to manage a single scene
and its views. Although the class provides all we need to control a scene, Figure 5-24: ViewController class assigned to the initial scene
we do not work directly with objects created from it. Instead, we define a
subclass of UIViewController for each scene in the Storyboard and add our
own properties and methods to perform the tasks that are specific to the
scene and our app.
As we will see in further chapters, applications with only one scene are
rare. Usually, more scenes are added to the Storyboard to represent all the 
screens our users can navigate through to find the information they need.
To know which object controls each scene, the scenes are associated to a The ViewController class provided by the App template is almost empty,
view controller from the Identity Inspector panel. This is one of the except for the viewDidLoad() method. This is a method defined in the
configuration panels available in the Utilities Area when the scene is UIViewController class that is executed when the scene’s main view and its
selected. To open the panel, we must select the scene and click on the content are created and loaded into memory. We must override this
third button at the top of the Utilities Area (circled in Figure 5-23).
method in our subclass if we want to program our own response. Along
with this method, the class includes a few more to report other states.
Figure 5-23: Identity Inspector panel

viewDidLoad()—This method is called after the main view is loaded


into memory. It is the first method called on the object to indicate
that the view is ready to be shown on the screen.
viewWillAppear()—This method is called after the viewDidLoad()
method and before the main view is shown on the screen.
viewDidAppear()—This method is called after the main view is
 shown on the screen.
viewWillDisappear()—This method is called before the view is essential tasks necessary for the app to work properly, and that’s why we
removed from the screen. must always call them before executing our own code.
viewDidDisappear()—This method is called after the main view has In this example, our code gets a reference to the UIApplication object to read
been removed from the screen. the properties defined in its delegate. First, we obtain this reference by
reading the class' shared property. Next, we get a reference to the delegate
Usually we do not need other method than viewDidLoad() to set up the object from the delegate property of the UIApplication object and assign it to
scene. This is the reason why it is the only one declared by the template. the mydelegate constant (this is the object created from the class declared in
The following example modifies the ViewController class provided by the App the AppDelegate.swift file). Notice that we must cast this object as
template to read the property we added to the UIApplication delegate in AppDelegate with the as! operator because the AppDelegate object is a subclass

Listing 5-8 and print its value on the console as soon as the scene’s main of the UIResponder class and the delegate property returns a value of that
view is loaded. type. Finally, we use Optional Binding to read the basicSalary property in the
delegate and print its value on the console.
Listing 5-10: Adding our own code to the ViewController.swift file The process carried out by this application is as follows. When the app is
 executed, the system creates all the basic objects, including the UIApplication
import UIKit object and its delegate, and then calls the application(UIApplication,
class ViewController: UIViewController { didFinishLaunchingWithOptions:) method of the AppDelegate object to let us know
override func viewDidLoad() { that the app is ready. In this method, we initialize the basicSalary property
super.viewDidLoad()
(see Listing 5-8). When the execution of this method is over, the system
let app = UIApplication.shared looks in the Storyboard for the scene that has to show first and calls the
let mydelegate = app.delegate as! AppDelegate
viewDidLoad() method of its view controller. In this method, we get the value
if let salary = mydelegate.basicSalary { of the basicSalary property defined in the delegate object and print it on the
print("Basic Salary is \(salary)") // "Basic Salary is 30000.0"
} console.
}
}
 Do It Yourself: The last example assumes that you have already
declared and initialized the basicSalary property in the app’s delegate,
The first thing we do inside the viewDidLoad() method of Listing 5-10 is to call as shown in Listing 5-8. Replace the code in your
the same method on the superclass (see super in Chapter 3). When a ViewController.swift file by the code in Listing 5-9. Select an iPhone
method is overridden, the original method is not executed anymore, but simulator from the Scheme and press the Play button to execute the
the original methods of some classes, like the UIViewController class, perform app (Figure 5-13). You should see the message “Basic Salary is

30000.0” printed on the console (Figure 5-12, Debug and Console import UIKit

Area). class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
IMPORTANT: The scenes defined in the Storyboard conform a path view.backgroundColor = .systemBlue
the user must follow to achieve a goal (e.g., access information, find }
}
a picture, etc.). The starting point of the path is determined by 
assigning the view controller object that controls the initial scene to
a property of the UIWindow object called rootViewController (see the This code produces the same effect we achieve by changing the view's
UIWindow class introduced before). This may be done background color from the Attributes Inspector panel, but now we did it
programmatically, but you can also do it from the Storyboard. All you from code, after the view is loaded. This is required to have a functional
need to do is to point the arrow on the screen to the scene you want application. From the Attribute Inspector panel, we set the interface's
to show first (Figure 5-19). If the arrow is not visible, you can specify initial configuration, but then we must adapt that configuration and
the scene you want to show first from the Utilities Area. Select the replace the content from code according to what the user requires or the
scene in the Storyboard (clicking on the bar at the top), go to the state of the app, as we will learn in the next section.
Utilities Area, open the Attributes Inspector panel (Figure 5-22), and
activate the option Is Initial View Controller. Do It Yourself: Update the ViewController class in your project with
the code in Listing 5-11. Run the application. You should see a blue
The purpose of a view controller is to manage the scene's main view and screen. Replace the systemBlue value by any other color and run the
its content. For this purpose, the UIViewController class defines the following application again.
property.

view—This property sets or returns the UIView object that represents


the scene's main view.

When the app is launched, a UIView object is created to represent the


scene's main view and this object is assigned to the view property. From this
property, we can modify the attributes of the view, as we did in previous
examples (see Listing 5-6). For instance, the following view controller
change the view's background to blue as soon as the view is loaded.

Listing 5-11: Modifying the main view from the view controller

5.4 Views Library
 

Templates include everything we need to start building our application. We The Library is a popup window that opens when we press the Library
get a Storyboard with the initial scene, a view controller to manage the button on the right side of the toolbar (Figure 5-12, number 5). This
scene's main view and its content, and all the necessary configuration files. window presents a list with all the elements we can add to the Storyboard
and the interface, including views, controls, and scenes.
After the template is created, we are ready to work on our application, and
the first step is to add views to the scene to define the user interface. For
Figure 5-25: Library
this purpose, Xcode offers a tool called Library.

The elements in the Library are incorporated into the view by dragging and
dropping, as illustrated in Figure 5-26, below. If we want to drag more than
one element, we can press and hold down the Option key to keep the
window open.

Figure 5-26: Adding elements to the scene

but for testing purposes, we can expand the view by dragging the square
indicators around the label or define a specific size in points from the Size
Inspector panel (see Figure 5-22, right).

IMPORTANT: An element dropped inside a view becomes one of its


subviews. This is the same process performed by the addSubview()
method of the UIView object (see Listing 5-7), with the difference that
 this time everything is done visually. All the views and subviews
generated by this tool are stored as XML code and then the real
Once the element is dropped inside the view it becomes the selected objects are created when the application is launched.
element. Selected elements are surrounded by little squares that we can
drag to change the size of their views. We can also drag the element to a
new position and select another element by clicking on it.
Several elements may be selected at the same time by pressing and
holding down the Shift key on the keyboard. Some elements, such as labels
and buttons, allow us to double click on them to edit their content. For
instance, we can double-click on the label added to the scene in Figure 5-
26 and change the text to "My Title".

Figure 5-27: New text for a label

A label is a view that can display text on the screen. The view is the
rectangular area that determines the space occupied by the label, and the
text is the view's content. Therefore, the size of the text is independent of
the size of the view. If the view is not big enough to fit the text inside, the
text will be truncated, as in "My Ti...". In Chapter 6, we will learn how to
adapt the views in the interface to the space available and their content,
Guide Lines Outlets
 

While the element is being dragged over a view, Xcode shows lines to help As we already mentioned, the elements generated in the Storyboard,
us find the right place to drop it. There are lines that determine the including views, subviews, and their properties, are stored as XML code in
horizontal and vertical center of the view, the superior, inferior, and lateral
the storyboard file. Right after the app is launched, the objects for the
margins, and the position of one element relative to another. Figure 5-28
shows what we see when we drag a new label to the corner of the main main views and their content are created from this code. The UIView
view. objects that represent the main views are connected to their view
controllers through the view property (see Listing 5-11), but the objects that
Figure 5-28: Guide lines represent the subviews inside these main views (labels, buttons, etc.) are
not connected to our code. They did not exist when we were programming
our app (they were just XML code in the storyboard file), and therefore we
cannot reference these objects from our code. To solve this problem,
Xcode introduces the concepts of Outlets.
An Outlet is a property referencing an object in the user interface. These
are normal properties but prefixed by the @IBOutlet attribute to indicate to
Xcode that they are related to an element in the Storyboard. When the app
is launched, the objects are created from the Storyboard and then assigned

to their Outlets in our code. It is through these Outlets that we can access
the objects from code and read and set the values of their properties.
These lines become visible again when we drag the elements inside
the view to find the right location and when we expand the area of the There are different ways to create an Outlet and generate the connection.
view by dragging the little squares around it. The simplest is to drag a line from the element in the scene to our view
controller and let Xcode generate the codes on both ends. For this, we
need to divide the Editor Area in two parts by selecting the Assistant
option from the menu opened by the button at the editor's top-right
corner, as illustrated below.

Figure 5-29: Assistant Editor

 

Once the Assistant option is selected (Figure 5-29, number 1), the Editor Outlets, as well as properties, should always be created at the beginning of
Area is split in two sections, with the main file on the left and an additional the class. When we reach this place and release the mouse button, Xcode
file opened on the right. If the element selected on the left is a scene in the opens a popup window to configure the connection.
Storyboard, the right area automatically shows its view controller. Figure 5-
30, below, shows what we see when we open the Assistant Editor and Figure 5-32: Setting up a connection
select the scene with the label from Figure 5-26.

Figure 5-30: Assistant Editor

The window asks for the type of connection (Outlet or Outlet Collection)
and additional information to generate the code. To create an Outlet, we

must select the option Outlet in the Connection field, insert the name we
want to assign to the property, its data type (for labels is UILabel), and the
The connection between the elements in the view on the left and our code
on the right is established by dragging a line from the element to the class value that determines how the memory is going to be managed. The latter
while pressing and holding down the Control key on the keyboard. Xcode is usually specified as weak, because its container (the UIView object that
generates a blue line on the screen to show the connection that is being represents the main view) already has a strong reference to the object (see
created and to indicate where the property is going to be placed in our Memory Management in Chapter 3).
code. In the example depicted in Figures 5-31 and 5-32, a label with the text My
Title was added to the main view and then connected to the view
Figure 5-31: Connection between a label and its view controller controller with an Outlet called mytitle. When we click the Connect button
in the popup window, Xcode generates the code for the Outlet, as shown When an Outlet is created, Xcode generates code in our view controller
next. and in the storyboard file. Something to keep in mind is that deleting the
code for an Outlet in the view controller is not enough to break the
Listing 5-12: Adding an Outlet for a label to the view controller connection. This only deletes the connection in our code, but it does
 nothing to the XML code in the storyboard file. To also erase the
import UIKit connection in the Storyboard, we must click the mouse's right button
(Secondary click) when the pointer is over the element to open a popup
class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!
window where we can edit the element's configuration, as shown below.

override func viewDidLoad() { Figure 5-33: Popup window to manage connections in the Storyboard
super.viewDidLoad()
}
}

Do It Yourself: If you haven't already, drag and drop a label from


the Library into the scene in the Storyboard. Double-click the label to
change its text to "My Title". Open the Assistant Editor (Figure 5-29).
Position the mouse over the label and press and hold down the
Control key on your keyboard. Press the mouse button and drag a
line from the label to the beginning of the ViewController class. Release 
the mouse button when you see something like Figure 5-31. A popup
window like the one in Figure 5-32 will open. Define the Connection The popup window shows all the connections created for the selected
element. The name of the property is on the left and the name of the
as Outlet, the name as mytitle, the type as UILabel, and the Storage as
object is on the right. The name of the object includes a small x on the left
weak. Click the Connect button to generate the code. Your view
that is highlighted by a circle when we move the mouse over. Clicking on
controller should look like Listing 5-12.
this x erases the corresponding XML code in the storyboard file and
completely breaks the connection (we can also erase the element if we do
IMPORTANT: There are some names that are already in use by the not need it anymore, and the connections are erased with it).
UIKit classes or are reserved by the Swift language and we cannot
use in our own properties or methods. For example, you cannot give
a property the name title because the UIViewController class already
defines a stored property with that name. To avoid conflicts, always
use descriptive names such as homeTitle or titleLabel.

Document Outline Panel represents the area of the main view that is not superposed by toolbars or
navigation bars. And finally, is the item that represents our label. By

selecting any of these items in the Document Outline panel we are
selecting the element they represent. For instance, if we select the item
The Document Outline panel is an additional panel that opens on the left
that represents the label, the label is selected on the scene.
side of the Editor Area when we click on the button at the editor’s bottom-
The items take the name of the element they represent, and in the cases of
left corner (see Figure 5-21, number 1). It offers a quick access to all the
labels and buttons, they are named after the text assigned to the elements.
elements in the Storyboard and allows us to select them, connect them to
For example, the item that represents our label is called My Title, because
Outlets and Actions in our code, change their names to make them easy to
that’s the text assigned to the label. But we can change the name by
find, move them up or down in the hierarchy, delete them, and create
selecting the item and pressing the Return key on the keyboard.
constraints between them (we will study Actions later and constraints in
Chapter 6).
Figure 5-35: Changing the item’s name

Figure 5-34: Managing the elements of the scene

The items in the Document Outline panel represent the elements in the
Storyboard and therefore edit their properties, move them up or down the
 hierarchy, change their positions and sizes, and connect them with our
code. For instance, we can create an Outlet for our label by control-
The items are displayed in a hierarchical list. There are items at the top dragging a line from the item in the panel to the view controller.
of the hierarchy to represent each scene in the Storyboard (Figure 5-34,
number 1), and then items inside to represent the components of each Figure 5-36: Creating Outlets
scene. The items at the top are called View Controller, First Responder, and
Exit (the three buttons in the scene’s top bar). As we will see later, the First
Responder represents the element that first responds to user interaction
and the Exit control is used for navigation, but the View Controller
represents the scene’s view controller and therefore it contains the
elements of the interface. The first element on the list is the main view 
(View). Inside this item we find an item with the title Safe Area that
This is the same process introduced in Figure 5-32, but instead of dragging Labels
the line from the element in the scene, we do it form the item in the panel.

This is useful when elements on the interface overlap each other and are
hard to reach.
The label we have added to the scene in the previous section is probably
the most common element we can add to the user interface. Labels are
views that draw one or multiple lines of text on the screen. They can be
used to present information or to identify other elements on the interface.
Labels are created from the UILabel class, a subclass of UIView that adds the
additional functionality necessary to draw the text. The UILabel class
doesn't define its own initializer but we can create objects of this class
from code using the initializer inherited from the UIView class.

UILabel(frame: CGRect)—This initializer creates a UILabel object.


The frame argument defines the position and size of the view that
contains the text.

As seen in the previous section, labels can be added to the interface from
the Library. All we need to do is to drag and drop the option and the label
is included in the scene (see Figure 5-26).

Figure 5-37: Label option in the Library

After the label is created, we must assign a text to it and configure the
view. The following are some of the properties included by the UILabel class
for this purpose.

text—This property sets or returns the text displayed by the label.

attributedText—This property sets or returns the formatted text shadowColor—This property sets or returns the color of the
displayed by the label. shadow. It is an optional property of type UIColor. When its value is nil,
font—This property sets or returns the font used to display the label. the text is drawn with no shadow.
Its value is an object of the UIFont class (we will introduce this class shadowOffset—This property sets or returns the offset of the
next). shadow. Its value is a CGSize structure that establishes the horizontal
textColor—This property sets or returns the color of the text. It is of and vertical displacement.
type UIColor.
When the label is added to the scene from the Library, we can modify the
textAlignment—This property sets or returns the alignment of the values of most of these properties from the Attributes Inspector panel.
text. It is an enumeration of type NSTextAlignment with the values left (to
the left side of the view), center (to the center of the view), right (to the
Figure 5-38: Editing the properties of a label from the Utilities Area
right side of the view), justified (the last line of the paragraph is
aligned), and natural (uses the alignment associated with the text).
lineBreakMode—This property sets or returns the mode used to
display lines of text that go beyond the view's boundaries. It is an
enumeration of type NSLineBreakMode with the values byWordWrapping
(the word that does not fit is moved to the next line), byCharWrapping
(the character that does not fit is moved to the next line), byClipping
(characters that does not fit are not drawn), byTruncatingHead (the
beginning of the text is replaced by ellipsis), byTruncatingTail (the end of
the text is replaced by ellipsis), byTruncatingMiddle (the middle of the
text is replaced by ellipsis). 
numberOfLines—This property sets or returns the number of lines
allowed for the text. If the text requires more lines than those set by This is the same we have done for the main view before (see Figure 5-22),
this property, it is truncated according to the mode selected by the but of course the options available are specific for labels. For example, we
can modify the text, color, and font of the label, and some aspects of the
lineBreakMode property. The value 0 declares unlimited lines.
label's view as well, such as the background color.
adjustFontSizeToFitWidth—This property sets or returns a
Boolean value that determines whether the font size needs to be Do It Yourself: Select the label on the scene. Insert a different text
decreased for the text to fit inside the view. It only works when the for the label and select a new color from the Color option. Click on
numberOfLines property is set to a value different from 0.
the T button in the Font option to see the values available (we will
learn more about these options next).

IMPORTANT: Classes like UILabel are subclasses of the UIView class.


Other similar classes like UIButton, for example, are subclasses of
UIControl, which is a subclass of UIView. Therefore, they not only have
access to their own properties, but also to the properties they
inherit from their superclasses. All these properties are available on 
the Attributes Inspector panel under the sections Control and View.
Do It Yourself: Update the ViewController class with the code in
From the Attributes Inspector panel, we can set the label's initial Listing 5-13 and run the application. Something you may notice right
conditions, but if our app needs to change these attributes later, we must away, is that the size of the label's view remains the same, no matter
do it from code by modifying the values of the UILabel properties. For the text we assign to it. If the new text is longer than the original, it
instance, we can assign a new text to the label as soon as the view is won't fit inside the view and it would be truncated, as explained
loaded. before. To see the full text on the screen, expand the label's view by
dragging the square indicators on the sides or define the size in
Listing 5-13: Defining the label's text from code points from the Size Inspector panel (see Figure 5-22, right). We will

learn how to adapt the views to the space available in Chapter 6.
import UIKit

class ViewController: UIViewController { The size of the text is determined by the font set by the system. If we want
@IBOutlet weak var mytitle: UILabel!
to set a different size or font type, we must assign a new UIFont object to
override func viewDidLoad() { the font property of the UILabel object. UIFont objects are not only assigned
super.viewDidLoad() to labels but also to other elements that display text on the screen. They
mytitle.text = "Hello World"
} contain properties and methods to store and manage fonts. The following
} are the most frequently used initializer and type methods included in the

UIFont class to create these objects.

Figure 5-39: Label with text defined from code


UIFont(name: String, size: CGFloat)—This initializer creates a
UIFont object with the font referenced by the name argument and a
size determined by the size argument. The name argument is a string
with the font's PostScript name.

labelFontSize—This type property returns a CGFloat value with the application implements Dynamic Types, it can respond to these changes
size of the font used by the system to create UILabel objects. and show the text on the screen with the size preferred by the user.
To assign a new font to a label, we must create the UIFont object with the
buttonFontSize—This type property returns a CGFloat value with the
styles we want and then assign it to the label’s font property, as shown next.
size of the font used by the system to create UIButton objects
(buttons).
Listing 5-14: Creating dynamic font types
systemFontSize—This type property returns a CGFloat value with the 
size of the font used by the system. import UIKit

preferredFont(forTextStyle: TextStyle)—This type method class ViewController: UIViewController {


returns a UIFont object with the font associated to the style specified @IBOutlet weak var mytitle: UILabel!

by the forTextStyle argument and a size specified by the user in override func viewDidLoad() {
Settings. The argument is a property of a structure called TextStyle super.viewDidLoad()
let myfont = UIFont.preferredFont(forTextStyle: .headline)
included in the UIFont class. The properties available are body, callout, mytitle.text = "Hello World"
caption1, caption2, footnote, headline, subheadline, largeTitle, title1, title2, title3. mytitle.font = myfont
}
systemFont(ofSize: CGFloat)—This type method returns a UIFont }

object with the font set by default on the system and a size
determined by the ofSize argument. The code in Listing 5-14 creates a font with the headline style. This is the
boldSystemFont(ofSize: CGFloat)—This type method returns a style used for titles and headers, but we can specify any style we want
UIFontobject with the font of type bold set by default on the system depending on the purpose of the text. For example, for long texts and
and a size determined by the ofSize argument. content the preferred style is body. Every style uses different font types and
they may look different, but the size is always determined by the system or
italicSystemFont(ofSize: CGFloat)—This type method returns a
the user from the Settings app.
UIFontobject with the font of type italic set by default on the system
and a size determined by the ofSize argument. Figure 5-40: Label with headline style

Although we can create a font object with the initializer or any of the
methods above, Apple recommends working with the preferredFont()
method. The fonts returned by this method are called Dynamic Types
because they are selected by the system according to the style specified by
the argument and the size set by the user. The Settings app offers an option
to change the font size for all the applications installed on the device. If our

Figure 5-41: Label with custom font and size

IMPORTANT: Fonts with dynamic types automatically adapt their



size to the size determined by the user from Settings (the option is
available in General / Accessibility / Large Text), but if changes are
performed after the app was installed and launched, we must listen
IMPORTANT: The font names for the UIFont initializer are the
to notifications from the system to force the app to adapt the fonts PostScript names. To find these names, open the Font Book
to the new size. We will learn how to listen to system notifications in application from your Applications folder, go to the View menu, and
Chapter 16. select the option Show Font Info. On the right column of the
window, you will see the information for the selected font, starting
by its PostScript name. Select the font you would like to include in
If we do not want to use Dynamic Types, we can define the font with a
your app to get its name. Most of the fonts in your computer are
specific family type and size using the UIFont initializer. The following
example creates the UIFont object with a font called Georgia-Italic and a available in iOS, but you can also add your own fonts. For more
size of 30 points. information, visit our website and follow the links for this chapter.

The system also provides fonts that were carefully selected to optimize the
Listing 5-15: Defining a specific font and size
design and produce a pleasant experience for the user. The UIFont class

import UIKit
includes several type methods to get an object with these settings, as
shown next.
class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!
Listing 5-16: Using the system’s standard font
override func viewDidLoad() { 
super.viewDidLoad()
import UIKit
let myfont = UIFont(name: "Georgia-Italic", size: 30)
mytitle.text = "Hello World"
mytitle.font = myfont class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!
}
}

override func viewDidLoad() { mytitle.text = "Hello World"


super.viewDidLoad() mytitle.font = myfont
let myfont = UIFont.systemFont(ofSize: 30) }
mytitle.text = "Hello World" }
mytitle.font = myfont 
}
}
 This example reads the buttonFontSize property to get the size of the font
used by the system to write the text inside buttons and assign it as the size
The systemFont() method applied in Listing 5-16 returns a UIFont object with of the myfont object. This is helpful when we want to present text along
the standard system font but with the size specified by the argument. with some buttons and we need to keep everything proportional.
As explained before, we always have to make sure that the size of the
Figure 5-42: Label with system font label’s view is big enough to display the text on the screen. But this is not
always possible. Sometimes the size of the label’s view cannot change or
there is not enough room on the interface to expand it. The UILabel class
provides the lineBreakMode property to determine how the text is going to
be processed when it is longer than the width of its view. The value by
default is byTruncatingTail, which means that the characters at the end of the
text are going to be replaced by ellipsis. Other frequent values are
byTruncatingMiddle to truncate the middle of the text, or byClipping to hide the
characters that go beyond the view's boundaries. The following example

assigns a longer text to the text property to see the effects of the line break
modes applied to the label.
This time we declared a fix value of 30, but we can take advantage of some
of the type methods of the UIFont class to set the size to some of the values
Listing 5-18: Clipping the label’s text
defined by the system.

import UIKit
Listing 5-17: Getting the font size used by default for buttons
 class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!
import UIKit
override func viewDidLoad() {
class ViewController: UIViewController { super.viewDidLoad()
@IBOutlet weak var mytitle: UILabel! mytitle.text = "This is a Beautiful Life"
mytitle.lineBreakMode = .byClipping
override func viewDidLoad() { }
super.viewDidLoad() }
let size = UIFont.buttonFontSize 
let myfont = UIFont.systemFont(ofSize: size)
For this example, we have given the view a width of 173 points from the class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!
Size Inspector panel and set the size of the font to 20 from the Attributes
Inspector panel, so the view is not big enough to show the full text and override func viewDidLoad() {
super.viewDidLoad()
therefore the text is clipped, as shown in Figure 5-43.
mytitle.text = "Hello World"
mytitle.font = UIFont.systemFont(ofSize: 30)
Figure 5-43: Label clipped mytitle.textColor = UIColor.systemYellow
mytitle.shadowColor = UIColor.systemGray
mytitle.shadowOffset = CGSize(width: 2, height: 2)
}
}

Figure 5-44: Label with a shadow

Do It Yourself: Update the ViewController class with the code in


Listing 5-18. Select the label and change the size of the font to 20
from the Attributes Inspector panel (Figure 5-38). With the label
selected, open the Size Inspector panel and assign a width of 173 
points (Figure 5-22, right). Run the application. You should see the
text clipped, as illustrated in Figure 5-43. Try every possible value for These properties assign the styles to the whole text. Every time a new font,
the lineBreakMode property to see how they work. size or color is assigned to the UILabel object, all the characters in the text
are affected. If we want some parts of the text to have a different style, we
must create a separate label or assign the text to the attributedText property
The UILabel class includes more properties to customize the text, including
changing its color by assigning a UIColor object to the textColor property, or instead. This property takes a value of type AttributedString; a structure that
contains the text and all the attributes that must be applied to it. The
adding a shadow with the shadowColor and shadowOffset properties, as shown
following are some of the structure's initializers.
next.

Listing 5-19: Customizing the text AttributedString(String)—This initializer creates an AttributedString


 structure with the text specified by the argument.
import UIKit AttributedString(String, attributes: AttributeContainer)—This
initializer creates an AttributedString structure with the text specified by

the first argument. The attributes argument is a container for all the 
attributes we want to apply to the text. import UIKit

class ViewController: UIViewController {


The attributes are determined by attribute keys defined in what is called an @IBOutlet weak var mytitle: UILabel!
Attribute Scope. There is an enumeration called AttributeScopes with override func viewDidLoad() {
properties that contain an attribute scope for each platform (UIKit, appKit, super.viewDidLoad()
SwiftUI, and Foundation). The attribute scope for UIKit is stored in the uiKit var mytext = AttributedString("Hello World!")
mytext.font = UIFont.systemFont(ofSize: 24)
property. This is a structure of type UIKitAttributes with properties that mytext.foregroundColor = .systemRed
represent all the attribute keys available for UIKit. The following are the mytitle.attributedText = NSAttributedString(mytext)
}
most frequently used. }

font—This property defines the text's font attribute. It is a value of
type UIFont. The code in Listing 5-20 creates an AttributedString structure with the text
"Hello World!" and then assigns a system font of 24 points and the
backgroundColor—This property defines the text's background
systemRed color to the text. Notice that the attributedText property of the
color attribute. It is a value of type UIColor. UILabel object takes an NSAttributedString object. This is an old Foundation
foregroundColor—This property defines the text's foreground color class not compatible with AttributedString values, but the class includes an
attribute. It is a value of type UIColor. initializer we can use to convert an AttributedString structure into an
NSAttributedString object, as illustrated in this example. The result is shown in
strokeColor—This property defines the text's stroke color attribute.
It is a value of type UIColor. Figure 5-45.

strokeWidth—This property defines the text's stroke width Figure 5-45: Label with attributes
attribute. It is a value of type CGFloat.
shadow—This property defines the text's shadow attribute. It is a
value of type NSShadow.

To show a label with an attributed string, we must instantiate an


AttributedString structure with the text we want to display, modify the
structure's properties to define the attributes, and then assign the
structure to the label’s attributedText property. 

Listing 5-20: Assigning attributes to a label


The previous example assigns the attributes to the entire text, but the
purpose of the AttributedString structure is to assign different attributes to Figure 5-46: Attributes assigned to a range of characters
ranges of characters in the text. Like the String structure, the AttributedString
structure identifies the positions of the characters with index values. To
modify a specific portion of the text, all we need to do is to define a range
of indexes that determine the positions of the first and last characters of
the string we want to modify. The AttributedString structure includes a handy
method for this purpose.

range(of: String)—This method returns a Range structure defined



with the indexes of the first and last characters of the string specified
by the of argument. Attributes may be added to the label programmatically, as we have done so
far, or from the Attributes Inspector panel (see Figure 5-38). If the label
Listing 5-21: Assigning attributes to a range of characters already contains some attributes and a new AttributedString structure is
 assigned to it, the old attributes will be removed. To preserver the current
import UIKit attributes, we must get the current attributed string from the label's
class ViewController: UIViewController { attributedText property, add the new attributes, and assign the string back to
@IBOutlet weak var mytitle: UILabel! the label. The problem is that the value returned by this property is an
NSAttributedString object. To convert this object into an AttributedString
override func viewDidLoad() {
super.viewDidLoad() structure, the structure includes the following initializer.
var mytext = AttributedString("Hello World!")

if let range = mytext.range(of: "World") { AttributedString(NSAttributedString, including:


mytext[range].font = UIFont.systemFont(ofSize: 24)
mytext[range].foregroundColor = .systemRed
AttributeScope.Type)—This initializer creates an AttributedString
mytitle.attributedText = NSAttributedString(mytext) structure from the NSAttributedString object specified by the first
}
}
argument. The including argument is the scope's data type that
} defines the attribute keys used to generate the attributes for the

string. Only the attributes in the NSAttributedString object that match
the attributes in the scope are applied to the AttributedString structure.
This example creates the AttributedString structure as before, and then gets
the range of indexes that determines the position of the string "World".
The following example gets the attributed string from the label, converts it
With this value, we assign a system font and a color to the text, but only to
the characters inside the range. into an AttributedString structure, change its foreground color to blue, and
assign it back to the label.

Listing 5-22: Assigning additional attributes to the label Do It Yourself: Select the label and go to the Attributes Inspector
 panel (Figure 5-38). Change the Text option from Plain to Attributed,
import UIKit select the word "World" and change the font size to 36. You should
class ViewController: UIViewController { see something like Figure 5-47, left. Update the ViewController class
@IBOutlet weak var mytitle: UILabel! with the code in Listing 5-22 and run the application. The label's
override func viewDidLoad() { color should now be blue, but the size of the text should remain the
super.viewDidLoad() same, as illustrated in Figure 5-47, right.
if let oldtext = mytitle.attributedText {
if let attrText = try? AttributedString(oldtext, including: \.uiKit) {
var mytext = attrText As we do with String values, we can work directly with string indexes to
mytext.foregroundColor = .systemBlue
mytitle.attributedText = NSAttributedString(mytext) modify any character we want. For this purpose, the AttributedString
} structure includes two properties, characters and runs. These properties
}
}
return structures with sets of values that reference the characters in the
} string and the runs, which contains the attributes and the text to which
 they are applied. The characters property returns a structure of type
CharacterView. The following are some of the properties and methods
The AttributedString initializer takes an NSAttributedString object and the scope
provided by this structure to manage the indexes.
that defines the attribute keys we want to use to convert the text's
attributes. In this case, we are using UIKit types, so we specify the keypath
indices—This property returns a collection with the characters'
to the uiKit property of the AttributeScopes enumeration. This tells the
indexes.
initializer to use the attribute keys defined in the UIKitAttributes structure.
After the AttributedString structure is ready, we assign it to a variable to be count—This property returns an Int value with the number of
able to modify its attributes, change the foreground color to systemBlue, turn characters in the string.
the value into an NSAttributedString object, and assign it back to the label. As startIndex—This property returns the index of the first character. It
a result, all the previous attributes are preserved, but the text is now is a value of type Index.
shown in blue.
endIndex—This property returns the index of the last character. It is
Figure 5-47: Label with old and new attributes a value of type Index.
first—This property returns the string's first character. It is a value of
type Character.
last—This property returns the string's last character. It is a value of
 type Character.
firstIndex(of: Character)—This method returns an Index value with 

the index of the first character that matches the character specified
In this example, we get the index of the string's first character from the
by the of argument.
startIndex property, then calculate the index of the string's fifth character
index(Index, offsetBy: Int)—This method increments the index with the index(offsetBy:) method, and finally assign the attributes to the
specified by the first argument the amount of units specified by the characters in the range determined by these two indexes. In this case, this
offsetBy argument and returns a new Index value with the result. corresponds to the word "Hello".
index(after: Index)—This method increments the index specified by
the after argument one unit and returns a new Index value with the Figure 5-48: Attributes assigned to a custom range of characters
result.
index(before: Index)—This method decrements the index specified
by the before argument one unit and returns a new Index value with
the result.

The process to assign attributes to a range of characters is the same as


before, but this time we must defined the range ourselves with the indexes

obtained from the CharacterView structure.

Most of the AttributedString properties require values of types we are familiar


Listing 5-23: Assigning attributes to a customized range of characters
with, such as UIFont or UIColor, but some are of a more specific type. For

example, the shadow property requires an object of the NSShadow class. This
import UIKit
is a simple class with a few properties to configure the characteristics of a
class ViewController: UIViewController { shadow.
@IBOutlet weak var mytitle: UILabel!

override func viewDidLoad() { shadowColor—This property defines the color of the shadow. It is a
super.viewDidLoad()
var mytext = AttributedString("Hello World!")
value of type UIColor.

let startIndex = mytext.characters.startIndex


shadowOffset—This property defines the offset of the shadow. It is
let endIndex = mytext.characters.index(startIndex, offsetBy: 5) a value of type CGSize, which determines the horizontal and vertical
mytext[startIndex...endIndex].font = UIFont.systemFont(ofSize: 24)
displacement.
mytext[startIndex...endIndex].foregroundColor = .systemRed shadowBlurRadius—This property defines the shadow's blur effect.
mytitle.attributedText = NSAttributedString(mytext)
} It is a value of type CGFloat.
}

Effects like shadows are noticeable when they are applied to big fonts. In If our interface contains multiple labels that require the same attributes,
the following example, we assign the dynamic type largeTitle to the text's instead of applying the attributes one by one to each label, we can create a
font and then apply a gray shadow with a horizontal and vertical offset of 2 container with the AttributeContainer structure and apply all the attributes at
and a blur radius of 5. once. The following are some of the methods included in the AttributedString
structure to work with an attributes container.
Listing 5-24: Applying a shadow to the text
 setAttributes(AttributeContainer)—This method assigns the
import UIKit attributes in a container to the text. All the previous attributes are
class ViewController: UIViewController { removed.
@IBOutlet weak var mytitle: UILabel!
mergeAttributes(AttributeContainer)—This method assigns the
override func viewDidLoad() { attributes in a container to the text. The new attributes are merged
super.viewDidLoad()
var mytext = AttributedString("Hello World!")
with the previous attributes.
mytext.font = UIFont.preferredFont(forTextStyle: .largeTitle)
replaceAttributes(AttributeContainer, with:
let shadow = NSShadow() AttributeContainer)—This method replaces the attributes specified
shadow.shadowColor = UIColor.systemGray
by the container in the first argument with the attributes in the
shadow.shadowOffset = CGSize(width: 2, height: 2)
shadow.shadowBlurRadius = 5 container specified by the with argument.
mytext.shadow = shadow
mytitle.attributedText = NSAttributedString(mytext)
} Instead of assigning the attributes to the text, they are first defined in the
} container and later applied to the text with one of the methods listed

above, as in the following example.
Figure 5-49: Label with a shadow
Listing 5-25: Applying multiple attributes at once

import UIKit

class ViewController: UIViewController {


@IBOutlet weak var mytitle: UILabel!

override func viewDidLoad() {


super.viewDidLoad()
var mytext = AttributedString("Hello World!")

var container = AttributeContainer()
container.font = UIFont.preferredFont(forTextStyle: .body)
container.backgroundColor = .systemGray4
mytext.setAttributes(container) attributes—This property returns an AttributeContainer structure with
mytitle.attributedText = NSAttributedString(mytext) all the attributes assigned to the run's text.
}
} range—This property returns a Range structure that determines the
 location of the run in the string.

The properties of a container are the same we used before to assign the By iterating over the runs property, we can read the attributes currently
attributes directly to the text. The AttributeContainer structure is created first, assigned to the text and modify the sections one by one.
and then all the attributes are assigned to it as always. In this example, we
change the font style to body and the background color to gray. Listing 5-26: Modifying the attributes of a run

Figure 5-50: Attributes applied to a label from a container import UIKit

class ViewController: UIViewController {


@IBOutlet weak var mytitle: UILabel!

override func viewDidLoad() {


super.viewDidLoad()
var mytext = AttributedString("Hello World!")

if let range = mytext.range(of: "World") {


mytext[range].backgroundColor = .systemRed
}
 for run in mytext.runs {
if run.attributes.backgroundColor == .systemRed {
So far, we have worked with the string's characters, accessible from the let range = run.range
mytext[range].backgroundColor = .systemGray4
characters property, but there is also the runs property which returns a }
collection of runs that we can use to edit the attributes and the text. Runs }
mytitle.attributedText = NSAttributedString(mytext)
are the sections in the text with a unique combination of attributes. For }
instance, if we assign a red color to the string "World!" and keep the rest }

with the color by default, we will have two runs, one with the string
"World!" and the foreground color red, and another with the rest of the
The code in Listing 5-26 assigns the systemRed color to the word "World" and
text and the foreground color by default.
then looks for the run with that attribute to change the value to systemGray4.
Runs are defined by the Run structure, which includes the following
The run we are looking for can be found by checking the backgroundColor
properties.
property of the run's AttributeContainer structure. If the value is systemRed, it
means that the run corresponds to the word "World" so we get the run's

range and assign a new background color to that section of the string. As a Actions
result, the background of the word "World" is initially red, but it is changed

to gray before assigning the text to the label.
Some elements of the interface are not limited to show text or graphics on
IMPORTANT: The AttributedString structure also includes initializers
the screen; they can also process user interaction. Buttons, Switches,
to assign attributes to the text with a markup language. These are
Segmented Controls, and others are designed to respond to the user and
special characters that can be inserted directly into the string to
fire an event to report the situation to the application. For instance, a
define the attributes. For more information, visit our website and
button performs an action when pressed, such as saving values in a
follow the links for this chapter.
database, or replacing the current scene by another one. To perform these
actions, we need to connect the button in the interface with a method in
our code. For this purpose, Xcode implements Actions.
Adding an Action to an element is like adding an Outlet. We must drag a
line from the control to the view controller class while pressing and holding
down the Control key, and then provide the information for the
connection. For instance, in the following example, we have changed the
label's text to "Pressed: 0" and added a button below with the title "Press
Here". Figure 5-51 shows what we see when we try to add an Action to the
button.

Figure 5-51: Adding an Action for a button

The first thing we need to do in the popup window is to select the type of
the connection as Action. In the options, we must insert the name of the
method that will be executed when the action is performed, the type of
the object that performed the action, and the type of event that will
trigger the action (Buttons respond to the Touch Up Inside event, but there
are others, as we will see later). The last option, Arguments, determines is incremented by 1, and the text in the mytitle label is updated with the
the values we want to send to the method. The option includes three result.
possible values: None (no value is sent to the method), Sender (a reference
to the object that represents the control is sent to the method), and Figure 5-52: Button performing an action
Sender and Event (a reference to the object that represents the control and
information about the event that triggered the action are sent to the
method). By default, this value is set to Sender, which will add to the
method a parameter called sender to receive a reference to the object 
(buttons are created by objects of type UIButton, as we will see later).
After all the information is declared, Xcode generates the codes in both Do It Yourself: Change the label's title in the Storyboard to
ends (the Storyboard and the view controller). The declaration of the "Pressed: 0". Open the Library and drag the option called Button to
method in our view controller is like any other, but it is prefixed by the the scene. You should see something like the interface in Figure 5-
@IBAction attribute to establish its relationship with the element in the 51. Create an Action for the button with the name showCounter, the
Storyboard. Type UIButton, the Event Touch Up Inside, and the rest of the values
by default. Update the ViewController class with the code in Listing 5-
Listing 5-27: Defining an Action for the button 27. Run the application and press the button. You should see the
 value on the label change, as illustrated in Figure 5-52.
import UIKit

class ViewController: UIViewController {


@IBOutlet weak var mytitle: UILabel!
var counter: Int = 0

@IBAction func showCounter(_ sender: UIButton) {


counter += 1
mytitle.text = "Pressed: \(counter)"
}
}

Along with the Outlet for the label, the view controller in Listing 5-27
includes a property called counter to count the number of times the user
pressed the button. When the button is pressed, the UIButton object that
represents the button added to the scene in the Storyboard calls the
showCounter() method in our view controller, the value of the counter property

Buttons

The button implemented in the previous section is a control created from


the UIButton class. There are multiple types of buttons available, with 
different styles and functionalities. The following is the initializer provided
by the class to create a button from code. The following are some of the properties included in the Configuration
structure to modify the characteristics of these buttons.
UIButton(configuration: Configuration, primaryAction:
UIAction?)—This initializer creates a UIButton object with the title—This property sets or returns a string with the button's title.
configuration specified by the configuration argument, and the action subtitle—This property sets or returns a string with the button's
specified by the primaryAction argument. subtitle.
attributedTitle—This property sets or returns the button's
The configuration is determined by a structure defined in the UIButton class
attributed title. It is a value of type AttributedString.
called Configuration. The structure includes the following type methods to
get a predefined configuration for every type of button available. attributedSubtitle—This property sets or returns the button's
attributed subtitle. It is a value of type AttributedString.
plain()—This type method creates a configuration for a button with a titlePadding—This property sets or returns a CGFloat value that
transparent background. determines the distance between the button's title and subtitle.
filled()—This type method creates a configuration for a button filled titleAlignment—This property sets or returns a value that
with a background color. determines the title's alignment. It is an enumeration of type
gray()—This type method creates a configuration for a button with a TitleAlignment with the values automatic, center, leading, and trailing.

gray background.
Buttons can also include an image on the sides. The Configuration structure
tinted()—This type method creates a configuration for a button with
includes the following properties to set up this image.
a tinted background color.
image—This property sets or returns the buttons image. It is a
Of course, these buttons can be added to the scene in the Storyboard from
UIImage object.
the Library.
imagePadding—This property sets or returns a CGFloat value that
Figure 5-53: Button options in the Library determines the distance between the image and the text.
imagePlacement—This property sets or returns a value that
determines the position of the image in relation to the text. It is an IMPORTANT: Some of these properties work with leading and trailing
NSDirectionalRectEdge structure with the properties all, bottom, leading, top, values. Instead of using names like left and right, UIKit implements
and trailing. these values because the side they reference depends on the human
language set on the device. In English, for example, leading
showsActivityIndicator—This property sets or returns a Boolean
represents the left side of the element and trailing the right, but this
value that determines if the button will include an activity indicator (a
may change for other languages. We will learn more about leading
wheel that spins to indicate that an operation is in progress). and trailing in Chapter 6.

The Configuration structure also includes properties to configure the button. In UIKit, the most common way to incorporate a button into the user
interface is by dragging the option from the Library to the scene in the
baseBackgroundColor—This property sets or returns the button’s Storyboard, as we have done for the label and the button in previous
background color. It is a value of type UIColor. The color automatically examples. For instance, we can implement the previous interface, but now
adapts to the state of the button (normal, pressed, and disabled). with two buttons, one to increment the value on the label and another to
baseForegroundColor—This property sets or returns the color of decrement it.
the button’s content. It is a value of type UIColor. The color
Figure 5-54: Interface to test buttons
automatically adapts to the state of the button (normal, pressed, and
disabled).
cornerStyle—This property sets or returns a value that determines
the style of the corners. It is an enumeration of type CornerStyle with
the values dynamic, fixed, capsule, large, medium, and small.
buttonSize—This property sets or returns a value that determines
the size of the button. It is an enumeration of type Size with values
that define predefined sizes. The values available are large, medium, mini,
and small.
contentInsets—This property sets or returns a value that 
determines the padding around the button’s content. It is a structure
of type NSDirectionalEdgeInsets. The structure includes the following For this application to work, we need to define two Actions in the view
initializer to set the padding for each side: NSDirectionalEdgeInsets(top: controller, one for the Add button and another for the Remove button, as
CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat). shown next.

Listing 5-28: Responding to multiple buttons


 By default, the buttons are configured with only a title, but they can
import UIKit contain a title, a subtitle, and an image.
class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel! Figure 5-55: Button’s configurations
var counter: Int = 0

@IBAction func addToCounter(_ sender: UIButton) {


counter += 1
mytitle.text = "Pressed: \(counter)"
} 
@IBAction func removeFromCounter(_ sender: UIButton) {
counter -= 1
mytitle.text = "Pressed: \(counter)" When we are designing the interface in the Storyboard, the button’s
} configuration can be modified from the Attributes Inspector panel. Most of
}

the properties provided by the Configuration structure are available in this
panel, as shown below.
When the Add button is pressed, the addToCounter() method is executed, the
value of the counter property is incremented, and the label is updated with Figure 5-56: Configuration properties in the Attributes Inspector panel
the result. If the Remove button is pressed instead, the removeFromCounter()
method is executed, the value of the property is decremented, and the
label is updated again with the new value.

Do It Yourself: Keep the label but remove the button from the
interface of the previous example. Add a Filled button and a Tilted
button below the label. Double-click the buttons to change their
titles to Add and Remove. Drag the squares around the elements to
expand their views and get an interface like the one in Figure 5-54. 
Remove the Action added to the view controller in the previous
example and create an Action called addToCounter() for the Add button The first option sets the type of button we want to create. It includes some
and another called removeFromCounter() for the Remove button. standard designs and provides compatibility with older systems. To
Complete the ViewController class with the code in Listing 5-28. Run customize our button, we need to set the type as System (Figure 5-56,
the application and press the buttons. You should see the value on number 1). Next, we can determine the style (Plain, Gray, Tinted, or Filled)
the label change. and the type of text we want to use for the title (Plain or Attributed). Next,
there are two fields to insert the button’s title and subtitle (Figure 5-56,
number 2). To include an image, there is an Image option below. The field
class ViewController: UIViewController {
can take custom images or images defined by the system. The images @IBOutlet weak var addButton: UIButton!
provided by the system are called SF Symbols. These symbols are identified @IBOutlet weak var mytitle: UILabel!
var counter: Int = 0
by a name and are scalable, which means that the image adapts to the size
of the text. For instance, the image added to the button in Figure 5-55 is @IBAction func addToCounter(_ sender: UIButton) {
called trash.fill. Once we select the image, it is added to the button in the counter += 1
mytitle.text = "Pressed: \(counter)"
position determined by the Placement option (we will learn more about SF
Symbols and how to include our own images into the project later in this var configuration = addButton.configuration
configuration?.title = "Adding \(counter)"
chapter). addButton.configuration = configuration
Of course, all this customization can be made from code. The UIButton class }
includes the following properties and methods to update the configuration @IBAction func removeFromCounter(_ sender: UIButton) {
counter -= 1
of a button. mytitle.text = "Pressed: \(counter)"
}
}
configuration—This property sets or returns a Configuration structure 
with the button’s configuration.
configurationUpdateHandler—This property sets or returns a In this example, we get the button's current configuration, modify the
value of the title property with a string that includes the current value of
closure used to update the button’s configuration. The closure
the counter property, and then assign the configuration back to the button.
receives a value with a reference to the button.
As a result, every time the button is pressed, the title is updated to display
setNeedsUpdateConfiguration()—This method asks the system to the state of the counter.
update the button’s configuration. Notice that when the Remove button is pressed, the Add button is not
updated. When changes in the configuration depend on actions unrelated
All we need to do to change the button's configuration is to get the current to the button, we can define the configuration from the
configuration from the configuration property, modify the properties we want configurationUpdateHandler property and then call the setNeedsUpdateConfiguration()
to change, and assign the configuration back to the button. If the button is method to force an update. The closure assigned to the
added to the scene in the Storyboard, as in this example, we need an configurationUpdateHandler property is executed every time the button is
Outlet to access it. The following example includes an Outlet connected to pressed and when the setNeedsUpdateConfiguration() method is called, as shown
the Add button of the interface in Figure 5-54 and change its title when the in the following example.
button is pressed.
Listing 5-30: Updating the button’s configuration
Listing 5-29: Modifying the button's configuration 
 import UIKit
import UIKit

class ViewController: UIViewController { The closure assigned to the configurationUpdateHandler property is executed
@IBOutlet weak var addButton: UIButton!
@IBOutlet weak var mytitle: UILabel! every time the Add button is pressed, so there is nothing we need to do
var counter: Int = 0 from the addToCounter() method to update the button, but to update the
Add button when the Remove button is pressed, we must call the
override func viewDidLoad() {
super.viewDidLoad() setNeedsUpdateConfiguration() method on the Add button, as we did in the
addButton.configurationUpdateHandler = { [unowned self] button in removeFromCounter() method of Listing 5-30. The result is shown below.
var current = button.configuration
current?.title = "Add Me"
current?.showsActivityIndicator = counter > 0 ? true : false Figure 5-57: New button configuration
current?.imagePlacement = .trailing
current?.imagePadding = 15
button.configuration = current
}
}
@IBAction func addToCounter(_ sender: UIButton) {
counter += 1
mytitle.text = "Pressed: \(counter)" 
}
@IBAction func removeFromCounter(_ sender: UIButton) {
counter -= 1 Do It Yourself: Connect the Add button to the view controller with
mytitle.text = "Pressed: \(counter)" an Outlet called addButton. Update the ViewController class with the
addButton.setNeedsUpdateConfiguration()
} code in Listing 5-30. Run the application and press the Add Me
} button. You should see the number on the label change and the

activity indicator appearing on the button’s right-hand side (see
The closure assigned to the configurationUpdateHandler property receives a Figure 5-57, right). Press the Remove button to reduce the value to 0
reference to the button that we can use to get the current configuration, or less. The indicator should be removed.
perform the changes, and assign the configuration back to the button, as
done before. In this example, we change the button’s title to “Add Me” and IMPORTANT: The closure in Listing 5-30 accesses the counter
add an activity indicator to the button depending on the value of the counter property defined in the ViewController class. To access this property,
property. If the value of counter is greater than 0, the indicator is added to the closure must reference the ViewController object with self. Using self
the button, otherwise it is removed. inside a closure can create a strong reference cycle. To avoid it, you
The Activity Indicator is an animated image that looks like a rotating can mark self with the unowned keyword between square brackets, as
wheel, so we can configure it the same way we do with images. In this we did in this example. If self is not required inside the closure, this
example, we assign the value trailing to the imagePlacement property, which declaration can be removed.
positions the indicator on the button’s right-hand side and assign a padding
of 15 points between the indicator and the title. Although most of the time the buttons will be added to the scenes in the
Storyboard, there are times when an application needs to define a button
and its action from code. The buttons are created from the UIButton buttonConfig.title = "Press Me"
buttonConfig.subtitle = "Do it now!"
initializer introduced before, and the actions are created from the UIAction buttonConfig.baseBackgroundColor = .systemBrown
class. When a button on a scene is connected to the view controller with
let mybutton = UIButton(configuration: buttonConfig, primaryAction: UIAction(handler: {
the @IBAction attribute, Xcode and the compiler take care of creating the [unowned self] action in
UIAction object for us, but when the button is defined from code, we must print("Value of counter is: \(counter)")
provide this object ourselves. For this purpose, the class includes the counter += 1
}))
following initializer. mybutton.frame = CGRect(x: 20, y: 50, width: 150, height: 50)
view.addSubview(mybutton)
}
UIAction(title: String, image: UIImage?, identifier: Identifier?, }
state: State, handler: Closure)—This initializer creates an action 

for a control. The title and image argument are the text and the image
The button in this example is configured with the title “Press Me”, the
we want to use to represent the control. The identifier argument is a
subtitle “Do It Now!”, and a brown background. After the Configuration
structure defined in the UIAction class that can be initialized with a
structure is ready, we create the UIButton object with this value and a
string to identify the action. The state argument is an enumeration
UIAction object with a closure that prints a message and increments the
that specifies the action's initial state. The possible values are on, off, value of the counter property when the button is pressed. Finally, the button
and mixed. And the handler argument is the closure or the function to is added to the main view with the addSubview() method and presented on
execute when the action is performed. The closure or function the screen (see Listing 5-7).
receives one value with a reference to the UIAction object.
Figure 5-58: Button added to the interface from code
Most of the arguments are optional. For instance, if we already have a
configuration for the button, we only need to provide the closure that
defines the action to perform when the button is pressed, as in the
following example.

Listing 5-31: Creating a button from code



import UIKit

class ViewController: UIViewController {
var counter: Int = 0
Do It Yourself: Remove all the elements from the previous
override func viewDidLoad() { interface. Update the ViewController class with the code in Listing 5-31.
super.viewDidLoad()
var buttonConfig = UIButton.Configuration.filled() Run the application. You should see the button in Figure 5-58.

IMPORTANT: In our example, before adding the button, we had to Figure 5-59: Button configured from an Action
specify the position and size by assigning a CGRect value to the frame
property (the UIButton class inherits this property from the UIView
class). This is necessary for the system to know where to position the
button, but it is not the recommended practice. We use it here for
didactic purposes, but the elements on the interface should be
positioned and sized with constraints or added as the content of
Stack Views, as we will see in Chapter 6.

If all we want is to create a simple button with a title and an image, we can
provide the button’s configuration from the UIAction object, as in the The UIButton class is not a subclass of UIView but an intermediate class called
following example. UIControl (see Figure 5-6). The UIControl class provides all the functionality
required by controls like buttons, switches, sliders, and more. By
Listing 5-32: Configuring a button with an Action implementing the properties and methods defined by this class, we have
 more options to manage the controls. The following are the most
import UIKit frequently used.
class ViewController: UIViewController {
var counter: Int = 0 isEnabled—This property sets or returns a Boolean value that
override func viewDidLoad() { indicates whether the element is enabled or not.
super.viewDidLoad()
let mybutton = UIButton(configuration: .filled(), primaryAction: UIAction(title: "Press isSelected—This property sets or returns a Boolean value that
Here", handler: { [unowned self] action in indicates whether the element is selected or not.
print("Value of counter is: \(counter)")
counter += 1 addAction(UIAction, for: UIControl.Event)—This method adds an
}))
mybutton.frame = CGRect(x: 20, y: 50, width: 150, height: 50) action to a control. The first argument defines the action, and the for
view.addSubview(mybutton) argument is a property of a structure called Event that specifies the
}
} event that triggers the action. The properties available are touchDown,
 touchDownRepeat, touchDragInside, touchDragOutside, touchDragEnter,
touchDragExit, touchUpInside, touchUpOutside, touchCancel, valueChanged,
In this example, we create the button with the configuration by default
menuActionTriggered, primaryActionTriggered, editingDidBegin, editingChanged,
returned by the filled() method and then define the button’s title and action
editingDidEnd, and editingDidEndOnExit.
from the UIAction object.
removeAction(UIAction, for: UIControl.Event)—This method console), and the event touchUpInside, which is fired after the button is
removes from the control the action specified by the first argument pressed and released, so the closure is going to be executed only when
and for the event specified by the for argument. these actions occur.

Because UIButton is a subclass of UIControl, all these properties and methods Do It Yourself: Remove any element in the scene and add a Filled
are available from the objects that represent the buttons. For instance, we button, as in the interface of Figure 5-54. Connect the button to the
can call the addAction() method on a button in a scene in the Storyboard to ViewController class with an Outlet called myButton. Update the class
define the button’s action from code. with the code in Listing 5-33 and run the application. Press the
The following example assumes that we have included a button to the button. You should see a message printed on the console.
scene from the Library and connected that button to the view controller
with an Outlet called myButton. Using this Outlet, we can add an action to IMPORTANT: The touchUpInside event is the most common for
the button, as shown next. buttons, but other controls usually implement the valueChanged event
that is fired every time the value managed by the control changes, as
Listing 5-33: Adding an action to a button from code we will see later.

import UIKit The UIControl class also includes the isEnabled property to enable or disable
class ViewController: UIViewController { the button. By default, all buttons are enabled, but we can assign the value
@IBOutlet weak var myButton: UIButton! false to this property to disable them.
var counter: Int = 0

override func viewDidLoad() { Listing 5-34: Disabling a button


super.viewDidLoad() 
myButton.addAction(UIAction(handler: { [unowned self] action in
print("Value of counter is: \(counter)") import UIKit
}), for: .touchUpInside)
} class ViewController: UIViewController {
} @IBOutlet weak var myButton: UIButton!

override func viewDidLoad() {
super.viewDidLoad()
This is a hybrid alternative from previous procedures. Instead of adding the myButton.addAction(UIAction(handler: { [unowned self] action in
button to the interface and connect it to the view controller with a print("Button Pressed")
self.myButton.isEnabled = false
@IBAction or programming the button and the action from code, we add the }), for: .touchUpInside)
button to the interface and add the action from code with the addAction() }
}
method provided by the UIControl class. The method is called with a UIAction 
object that performs the same action as before (print a message on the

In this view controller, we modify the action defined in the previous 


example to disable the button after it is pressed, but we can enable or import UIKit
disable the button from anywhere in the code, no matter how the action is
class ViewController: UIViewController {
implemented. @IBOutlet weak var myLabel: UILabel!
Another useful property included in the UIControl class is isSelected. This @IBOutlet weak var myButton: UIButton!

property can be used to turn a standard button into a toggle button (a override func viewDidLoad() {
button that represents two states, on or off). The UIButton class offers the super.viewDidLoad()
myButton.changesSelectionAsPrimaryAction = true
following property for this purpose. myButton.isSelected = false
}
@IBAction func turnOption(_ sender: UIButton) {
changesSelectionAsPrimaryAction—This property sets or returns if sender.isSelected {
a Boolean value that indicates whether the button is going to track a myLabel.text = "Is On"
} else {
selection (true) or perform the action (false). myLabel.text = "Is Off"
}
}
If we assign the value true to this property, the button toggles the value of }
its isSelected property every time it is pressed, but it doesn't perform any 
other action (the action assigned to the button is ignored), so we can use it
to allow the user to select between two states. The following example We can use any style we want to create a toggle button, but if we use a
defines a toggle button and displays the current state on the screen. Plain button, as we did in the interface of Figure 5-60, it will reflect the
current state.
Figure 5-60: Interface to test toggle buttons
Figure 5-61: Toggle button

 Do It Yourself: Remove the elements in the scene and add a label


and a Plain button, as in the interface of Figure 5-60. Connect the
We need to set the changesSelectionAsPrimaryAction to true, but we can also set
label to the view controller with an Outlet called myLabel and the
the initial state with the isSelected property, as shown next.
button with an Outlet called myButton. Update the ViewController class
Listing 5-35: Turning a button into a toggle button
with the code in Listing 5-35. Run the application and press the Listing 5-36: Displaying a menu with a button
button to toggle the state. 
import UIKit
Buttons can also open a contextual menu when pressed. The UIControl class class ViewController: UIViewController {
includes the following property to assign this functionality to a button. @IBOutlet weak var myButton: UIButton!

override func viewDidLoad() {


showsMenuAsPrimaryAction—This property sets or returns a super.viewDidLoad()
myButton.showsMenuAsPrimaryAction = true
Boolean value that indicates whether the button is going to show a
menu (true) or perform the action (false). myButton.menu = UIMenu(children: [
UIAction(title: "Option 1", handler: selectOption),
UIAction(title: "Option 2", handler: selectOption),
The menu is created from the UIMenu class. The following is the initializer UIAction(title: "Option 3", handler: selectOption)
with the arguments required to define contextual menus. ])
}
func selectOption(action: UIAction) {
UIMenu(title: String, children: [UIAction])—This initializer print(action.title)
}
creates a menu with the options specified by the arguments. The title }
argument defines the title displayed at the top of the menu (an empty 

string removes the title), and the children argument is an array of


The first thing we do in the view controller in Listing 5-36 is to assign the
objects that represent the options available.
value true to the button's showsMenuAsPrimaryAction property. This turns the
menu as the primary action and therefore other actions assigned to the
The UIButton class includes the following property to associate a menu with
button are ignored. The menu is created next and assigned to the menu
the button.
property. Notice that in this case we didn't need a title for the menu, so
the title parameter is ignored. Now the button shows a menu when
menu—This property sets or returns the UIMenu object associated to pressed and a message is printed on the console when the user selects an
the button. option.

The menu is created from a UIMenu object, and the options are created Figure 5-62: Contextual menu
from UIAction objects. The UIAction objects require a title or an image, and
the closure or the function that is going to be executed when the option is
selected. In the following example, all the options execute the same
function, but we can assign different functionality to each option if
necessary.

UIAction(title: "Option 1", identifier: UIAction.Identifier("1"), handler: selectOption),


UIAction(title: "Option 2", identifier: UIAction.Identifier("2"), handler: selectOption),
UIAction(title: "Option 3", identifier: UIAction.Identifier("3"), handler: selectOption)
])
}
func selectOption(action: UIAction) {
let id = action.identifier.rawValue
if id == "1" {
print("Option 1")
 }
}
}
Do It Yourself: Remove the elements in the scene and add a Tinted 
button from the Library. Connect the button to the view controller
with an Outlet called myButton. Update the ViewController class with the The Identifier structure includes an initializer that takes a string, which we
code in Listing 5-36. Run the application, press the button, and select can read later from the rawValue property. In this example, we identify the
an option. You should see the option's title printed on the console. actions with the values 1, 2, and 3, and then check the identifier from the
selectOption() method to print a message if the first option is selected.
The button generated by the previous examples is called Pull Down button
In the last example, we have printed on the console the text we get from
because it displays a menu below, but there is another version called Pop
the action's title property. This property returns the title assigned to the
Up that displays a popup menu and updates the button's title to reflect the
action when it was created. Along with the title, the UIAction class defines
option selected by the user. To turn a Pull Down button into a Pop Up
properties to access every value assigned to the action, including title, image,
button, all we have to do is to assign the value true to the
identifier, and state. From the values of these properties, we can identify the
changesSelectionAsPrimaryAction property (this is the same property used to
action and optimize the response. For instance, we can assign an identifier
define a toggle button, as we have seen in the example of Listing 5-35).
to each action and then read the value of this identifier to know which
option was selected by the user.
Listing 5-38: Defining a Pop Up button

Listing 5-37: Identifying the option selected by the user
import UIKit

import UIKit class ViewController: UIViewController {
@IBOutlet weak var myButton: UIButton!
class ViewController: UIViewController {
@IBOutlet weak var myButton: UIButton! override func viewDidLoad() {
super.viewDidLoad()
override func viewDidLoad() { myButton.showsMenuAsPrimaryAction = true
super.viewDidLoad() myButton.changesSelectionAsPrimaryAction = true
myButton.showsMenuAsPrimaryAction = true
myButton.menu = UIMenu(children: [
myButton.menu = UIMenu(children: [ UIAction(title: "Option 1", handler: selectOption),
UIAction(title: "Option 2", state: .on, handler: selectOption), print("Selected: \(selected.title)")
UIAction(title: "Option 3", handler: selectOption) }
]) }
} 
func selectOption(action: UIAction) {
print(action.title)
} The selectedElements property returns an array of UIMenuElement objects that
} we must cast to UIAction objects to access the option's values. In this

example, we get the first element of the array (only one option is selected),
get the UIAction object, and print the title on the console.
The only requirement to create a Pop Up button is to define the
changesSelectionAsPrimaryAction property, but we can also set the selected
Do It Yourself: Add another button to the scene. Connect the
option by including the state parameter, as we did for the Option 2 in this
button to the view controller with an Action called processOptions() and
example. If no option is selected, the first option is selected by default.
The Pop Up button works the same way as the Pull Down button, but the complete the method with the code in Listing 5-39. Run the
selected option is shown as the button's title. application, select an option from the menu, and press the new
button. You should see the name of the selected option printed on
Figure 5-63: Pop Up button the console.

We can also select an option from code. All we need to do is to get the
UIAction object that represents the option from the children property and
modify the action's state property. For example, the following method gets
the object that represents the second option (the option at index 1), casts
the object to a UIAction object, and assigns the value on to the action's state
 property to select that option. Now, every time the button is pressed, the
second option in the Pop Up button is selected.
The UIMenu class includes properties to manage the menu and the options.
The most useful are the children property to access the options and the Listing 5-40: Selecting an option from code
selectedElements property to return an array with objects that represent the 

selected options. The following is an action for an additional button that @IBAction func processOptions(_ sender: UIButton) {
let action = myButton.menu?.children[1] as? UIAction
will print the title of the selected option when the button is pressed. action?.state = .on
}

Listing 5-39: Getting the selected option

@IBAction func processOptions(_ sender: UIButton) {
if let selected = myButton.menu?.selectedElements.first as? UIAction {

Outlet Collections indicator on the left of the @IBOutlet property to the next element on the
list, as shown below.

Figure 5-65: Connecting multiple elements to the same Outlet
An Outlet Collection is a type of Outlet that can reference one or more
elements. It is useful when several elements of the interface must be
modified at the same time. The property created for the Outlet is an array
containing references to each element. By iterating over the array, we can
reach every element connected to the Outlet and access their properties.
The following example presents an interface with three buttons. The Say
Hello and Say Goodbye buttons print messages on the console and the 
Disable button is used to disable the first two.
After the Outlet Collection is defined and the first two buttons are
Figure 5-64: Interface to test Outlet Collections connected, we must create the Actions. Listing 5-41, next, shows the view
controller with the connections and all the Actions required for this
example.

Listing 5-41: Implementing an Outlet Collection



import UIKit

class ViewController: UIViewController {


@IBOutlet var buttons: [UIButton]!

@IBAction func sayHello(_ sender: UIButton) {


print("Hello!")
}
@IBAction func sayGoodbye(_ sender: UIButton) {
 print("Goodbye!")
}
@IBAction func disableButtons(_ sender: UIButton) {
To create the functionality for this interface, we need an Action for every for button in buttons {
button and an Outlet Collection for the first two. The Outlet Collection is button.isEnabled = false
created by dragging a line from the first element to the view controller, as }
}
we did before, but this time the Connection is set as Outlet Collection. The }
subsequent connections can be created by dragging a line from the 
The methods for the Say Hello and Say Goodbye buttons (sayHello() and Progress View
sayGoodbye()) print messages on the console, but the method for the Disable

button (disableButtons()) deactivates the other two buttons by modifying their
isEnabled property. To access the property of every button, we iterate over
UIKit includes the UIProgressView class to create a progress bar. This control
the buttons array created to store the references for the Outlet Collection.
was designed to show the progress of a task over time. The class includes
When the Disable button is pressed, the disableButtons() method assigns the
its own initializer, a property, and a method to set and read the progress.
value false to the isEnabled property of each button in the Outlet Collection
and therefore the first two buttons are disabled (they are shown in color
gray and do not respond to the user anymore).
UIProgressView(progressViewStyle: Style)—This initializer
creates a UIProgressView object with values by default and the style set
Do It Yourself: Clean the interface and code in your project. Drag by the argument. The argument is an enumeration called Style
and drop three buttons to the view to create the interface in Figure included in the UIProgressView class. The values available are default
5-64. Control-drag a line from the first button to the code and create (standard progress bar) and bar (progress bar for toolbars).
an Outlet Collection called buttons (remember to set the type of progress—This property sets or returns the current progress. The
connection as Outlet Collection). Control-drag a line from the possible values are between 0.0 and 1.0. The value by default is 0.0.
indicator on the left side of the @IBOutlet property to the second setProgress(Float, animated: Bool)—This method sets the value
button to add that button to the collection (Figure 5-65). Create the of the progress property. The first argument is a value between 0.0 and
Actions for every button and complete the methods with the code in 1.0, and the animated argument indicates if the transition will be
Listing 5-41. Every time you press any of the first two buttons, a animated.
message is printed on the console, but when you press the third
button, the other buttons are disabled. As always, the control can be added to a scene in the Storyboard from the
Library.

Figure 5-66: Progress View in the Library

In the interface, the bar looks like a line with two colors, one to indicate
the progress and another to provide a reference to how much is left to
process.

import UIKit

Figure 5-67: Progress bar in the scene class ViewController: UIViewController {


@IBOutlet weak var progressbar: UIProgressView!

override func viewDidLoad() {


super.viewDidLoad()
progressbar.progress = 0.2
progressbar.tintColor = UIColor.systemRed
progressbar.trackTintColor = UIColor.systemYellow
}
}


Figure 5-68: Custom progress bar
The class also includes the following properties to configure the
appearance of the bar.

progressTintColor—This property sets or returns the color of the


part of the progress bar that is filled. It is an optional of type UIColor.
trackTintColor—This property sets or returns the color of the part
of the progress bar that is not filled. It is an optional of type UIColor.
progressImage—This property sets or returns the image used to 
illustrate the part of the progress bar that is filled. It is an optional of
type UIImage.
Do It Yourself: Delete the elements in the scene. Drag a Progress
View from the Library to the scene. Connect it to the ViewController
trackImage—This property sets or returns the image used to class with an Outlet called progressbar. Update the view controller
illustrate the part of the progress bar that is not filled. It is an optional
with the code in Listing 5-42 and run the application. You should see
of type UIImage.
the interface in Figure 5-68.

The progress is set with values from 0.0 to 1.0. By default, the value is 0.5,
which sets the progress to 50%. This value can be changed from the
Attributes Inspector panel or in code, as shown next.

Listing 5-42: Customizing the progress bar



Activity Indicator


The Activity Indicator creates a spinning wheel that indicates that a task is
in progress, but unlike progress bars, this type of indicator has no implicit The activity indicator is usually implemented to show that a process has
limitations. We have introduced it before when working with buttons (see started. In the following example, we activate and deactivate the indicator
Listing 5-30). In that case, the indicator was automatically created by the every time a toggle button is pressed.
UIButton class, but we can initialize the object ourselves from the
UIActivityIndicatorView class. The following are the initializer and some of the Figure 5-70: Interface to test the Activity Indicator
properties and methods provided by the class.

UIActivityIndicatorView(style: UIActivityIndicatorView.Style)
—This initializer creates a UIActivityIndicatorView object with the style set
by the argument. The argument is an enumeration called Style
included in the UIActivityIndicatorView class. The values available are large
(large indicator) and medium (standard indicator).
color—This property sets or returns the color of the indicator.

hidesWhenStopped—This property sets or returns a Boolean value
that determines if the indicator is going to be hidden when the The view controller must include Outlets for the indicator and the button,
animation stops. and an action for the button to activate and deactivate the indicator when
isAnimating—This property returns a Boolean value that determines pressed.
whether the indicator is animating or not.
Listing 5-43: Working with an Activity Indicator
startAnimating()—This method starts the animation. 
stopAnimating()—This method stops the animation. import UIKit

class ViewController: UIViewController {


The following is the option in the Library to add the indicator to a scene in @IBOutlet weak var myButton: UIButton!
the Storyboard. @IBOutlet weak var activity: UIActivityIndicatorView!

override func viewDidLoad() {


Figure 5-69: Activity Indicator in the Library super.viewDidLoad()
myButton.changesSelectionAsPrimaryAction = true
activity.color = UIColor.systemRed

} Segmented Control
@IBAction func turnOnOff(_ sender: UIButton) {
if activity.isAnimating { 
activity.stopAnimating()
myButton.setTitle("Turn On", for: .normal)
} else { A Segmented Control creates a bar of interconnected buttons (if one
activity.startAnimating()
myButton.setTitle("Turn Off", for: .selected) button is pressed, the other buttons are deactivated). The control is
} defined by the UISegmentedControl class, which includes the following
}
}
initializer.

UISegmentedControl(frame: CGRect, actions: [UIAction])—
The code in Listing 5-43 configures the button as a toggle button with the This initializer creates a UISegmentedControl object that performs a
changesSelectionAsPrimaryAction property and then assigns a red color to the
specific action for each button. The frame argument determines the
indicator. When the app is launched, the indicator is not active, but if the position and size of the view, and the actions argument is an array of
button is pressed, the turnOnOff() method is called and the Activity Indicator
objects that define the buttons and the actions.
is activated. In this method, we check the current condition by reading the
isAnimating property, and then start or stop the indicator accordingly.
Each button of the segment is identified by an index starting from 0. The
properties and methods in the class use this index to access and modify
Do It Yourself: Delete previous elements in the scene. Drag an the buttons.
Activity Indicator and a Plain button from the Library to the scene
and connect them to the ViewController class with Outlets called numberOfSegments—This property returns the number of buttons
myButton and activity. Connect the button with an action called in the control.
turnOnOff(). Complete the view controller with the code in Listing 5-43
selectedSegmentIndex—This property sets or returns the selected
and run the application. Press the button. You should see the
button in the control.
indicator spinning.
setAction(UIAction, forSegmentAt: Int)—This method sets a new
action for a button. The first argument represents the action we want
to assign to the button, and the forSegmentAt argument determines
the index of the button to be modified.
insertSegment(action: UIAction, at: Int, animated: Bool)—This
method inserts a new button with the action and in the position
specified by the arguments. The action argument determines the
action for the button, the at argument specifies the index in which the
button will be inserted, and the animated argument determines
whether the process will be animated or not.
removeSegment(at: Int, animated: Bool)—This method removes
the button at the index specified by the at argument. The animated
argument indicates if the process will be animated.
setEnabled(Bool, forSegmentAt: Int)—This method sets the
condition of the button at the index specified by the forSegmentAt
argument. The first argument determines whether the button is 
enabled (true) or disabled (false).
The way a Segmented Control works is by keeping track of the selected
As with any other control, the Library offers an option to add a Segmented segment. An index value is assigned to each segment, from left to right,
Control to a scene in the Storyboard. starting from 0. When a new segment is selected, the value of its index is
assigned to the selectedSegmentIndex property of the UISegmentedControl object
Figure 5-71: Segmented Control in the Library that represents the element and the Value Changed event is fired. By
creating an Action that responds to this event, we can check the
selectedSegmentIndex property and execute the corresponding code according

 to its value.

By default, the control is added to the scene with two buttons, but we can Listing 5-44: Responding to the Value Changed event of a Segmented
define more from the Attribute Inspector panel and change their titles with Control

a double-click. For instance, the following interface includes a Segmented
Control with the two buttons by default but new titles, and a label we are import UIKit

going to modify according to the button selected by the user. class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!

Figure 5-72: Segmented Control with two buttons @IBAction func changeOption(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
mytitle.textColor = .label
} else if sender.selectedSegmentIndex == 1 {
mytitle.textColor = .systemRed
}
}
}

options.setAction(UIAction(title: "Black", image: nil, handler: { action in


print("Black Pressed")
The ViewController class in Listing 5-44 includes an Action called }), forSegmentAt: 0)
changeOption() for the Segmented Control. When the user selects a segment options.setAction(UIAction(title: "Red", image: nil, handler: { action in
print("Red Pressed")
by pressing a button, the Value Changed event is fired and the changeOption() }), forSegmentAt: 1)
method is executed. Inside this method, we check the value of the }
@IBAction func changeOption(_ sender: UISegmentedControl) {
selectedSegmentIndex property of the sender object (the Segmented Control) if sender.selectedSegmentIndex == 0 {
and change the color of the label accordingly (black for the segment at mytitle.textColor = .label
} else if sender.selectedSegmentIndex == 1 {
index 0 and red for the segment at index 1). mytitle.textColor = .systemRed
}
}
Do It Yourself: Delete the elements in the scene. Drag and drop a }
label and a Segmented Control from the Library into the scene 

(Figure 5-72). Double-click on the label and each segment to change


In this example, we connected the Segmented Control on the interface
the text. Create an Outlet for the label called mytitle and an Action for
with an Outlet and then call the setAction(forSegmentAt:) method twice to add
the control with the name changeOption() (Remember to set the Type an action to each button. The UIAction initializer includes a title, image, and
UISegmentedControl and the Event Value Changed). Update the view a closure to execute when the button is pressed, so the title of the
controller with the code in Listing 5-44. Run the application. You segment is modified every time a new action is assigned to it. In this case,
should be able to change the color of the label by pressing the we define the titles “Black” and “Red” and then print a message on the
buttons. console when the buttons are pressed.
Segmented Controls also include tools to add, remove, and edit segments.
The buttons included in a Segmented Control added from the Library are For example, we can insert a new segment with the insertSegment() method.
not associated to any action. If we want the buttons to perform a task
when pressed, we must add the actions from code with the Listing 5-46: Modifying a Segmented Control
setAction(forSegmentAt:) method, as shown in the following example. 
import UIKit
Listing 5-45: Adding actions to a Segmented Control
class ViewController: UIViewController {
 @IBOutlet weak var mytitle: UILabel!
import UIKit @IBOutlet weak var options: UISegmentedControl!

class ViewController: UIViewController { override func viewDidLoad() {


@IBOutlet weak var mytitle: UILabel! super.viewDidLoad()
@IBOutlet weak var options: UISegmentedControl! options.setAction(UIAction(title: "Black", image: nil, handler: { action in
print("Black Pressed")
override func viewDidLoad() { }), forSegmentAt: 0)
super.viewDidLoad() options.setAction(UIAction(title: "Red", image: nil, handler: { action in
print("Red Pressed")
}), forSegmentAt: 1)

let total = options.numberOfSegments


options.insertSegment(action: UIAction(title: "Blue", image: nil, handler: { action in
print("Blue Pressed")
}), at: total, animated: false)

options.setEnabled(false, forSegmentAt: 0)
}
@IBAction func changeOption(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex { 
case 0:
mytitle.textColor = .label
case 1:
mytitle.textColor = .systemRed
case 2:
mytitle.textColor = .systemBlue
default:
mytitle.textColor = .label
}
}
}

The view controller in Listing 5-46 defines the actions for the segments as
we did before, but then inserts a new segment at the end of the list. To
know the index, we read the numberOfSegments property. This property
returns the number of segments in the control, so we can use it as index to
add another one at the end.
To respond to the selection, we replace the if else statement from previous
examples with a switch statement and three cases, one per button. Notice
that the first segment is disabled with the setEnabled() method, so only the
Red and Blue buttons are available.

Figure 5-73: Segments added and modified from code

Switch we can replace the Segmented Control from the previous interface with a
Switch to change the label’s color between two values.

Figure 5-75: Interface to test a Switch
As its name indicates, the Switch control is a switch on the screen that the
user can turn on and off. UIKit includes the UISwitch class to create these
controls. The class doesn’t implement any initializer, but we can use the
UIView initializer to create a Switch from code or drag the option from the
Library.

UISwitch(frame: CGRect)—This initializer creates a UISwitch object


with the position and size specified by the argument.

Figure 5-74: Switch in the Library
When the Switch is turned on or off, it fires a Value Changed event, so we
can define an Action to listen to this event and read the isOn property to
check the state and perform a task accordingly.

Listing 5-47: Changing the color of a label with a Switch
Switches are turned on and off by the user, but the class includes some 
tools to modify the state from code. import UIKit

class ViewController: UIViewController {


isOn—This property sets or returns the state of the switch. It is a @IBOutlet weak var mytitle: UILabel!
Boolean value that determines the state of the switch; on (true) or off
@IBAction func turnSwitch(_ sender: UISwitch) {
(false). if sender.isOn {
mytitle.textColor = .label
setOn(Bool, animated: Bool)—This method sets the state of the } else {
switch. The first argument sets the switch on (true) or off (false), and mytitle.textColor = .systemRed
}
the animated argument determines if the transition will be animated. }
}

Switches can have only two states, on or off (true or false), and therefore
they are appropriate when only two conditions are possible. For instance,
When the switch is turned on, its background color changes. We can
modify this color and the color of the button using the following
properties.

onTintColor—This property sets or returns the color of the switch’s


background when it is turned on. It is an optional of type UIColor.
thumbTintColor—This property sets or returns the color of the
switch’s button. It is an optional of type UIColor.

In the following example, we connect the Switch with an Outlet to modify 


the appearance from code, and then apply custom colors to the knob and
the background. Do It Yourself: Recreate the interface from Figure 5-75. Create an
Outlet for the label called mytitle and an Action for the Switch with
Listing 5-48: Changing the colors of the switch the name mySwitch(), the Type UISwitch, the Event Value Changed, and
 the rest of the values by default. Complete the ViewController class
import UIKit with the code in Listing 5-48. Run the application. Every time you
class ViewController: UIViewController { turn the switch on or off, the color of the label should change
@IBOutlet weak var mytitle: UILabel!
@IBOutlet weak var mySwitch: UISwitch!
between black and red.

override func viewDidLoad() {


super.viewDidLoad()
let colorBackground = UIColor(red: 0.9, green: 0.9, blue: 1.0, alpha:1.0)
let colorButton = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 1.0)
mySwitch.onTintColor = colorBackground
mySwitch.thumbTintColor = colorButton
}
@IBAction func turnSwitch(_ sender: UISwitch) {
if sender.isOn {
mytitle.textColor = .label
} else {
mytitle.textColor = .systemRed
}
}
}

Figure 5-76: Switch with custom colors

Slider indicator to the corresponding position. The animated argument is a


Boolean value that determines if the change will be animated.

The Slider is usually applied to analog systems where a specific value is not
Sliders allow the user to select a value from a range of values. The control
required (e.g., volume control, brightness, etc.). For the following example,
is presented as a horizontal bar with a round indicator that points to the
we implement a Progress View along with a Slider with values by default.
selected value. It is defined by the UISlider class. We can create it from code
with the UIView initializer or add it to a scene from the option in the Library.
Figure 5-78: Progress View and Slider working together
UISlider(frame: CGRect)—This initializer creates a UISlider object
with the position and size specified by the argument.

Figure 5-77: Slider in the Library

 

By default, Sliders are created with a range of values between 0.0 and 1.0 Listing 5-49: Incrementing or decrementing a value with a Slider
and the indicator pointing at the value 0.5, but we can adapt the control to 

our needs from the Attributes Inspector panel or the properties and import UIKit

methods provided by the UISlider class. class ViewController: UIViewController {


@IBOutlet weak var progress: UIProgressView!

value—This property sets or returns the selected value. When the @IBAction func updateProgress(_ sender: UISlider) {
value is set, the indicator is moved to the corresponding position. It progress.progress = sender.value
}
takes a value of type Float. }

minimumValue—This property sets or returns the minimum value.
It is of type Float. When the user moves the pointer, the value in the value property changes,
maximumValue—This property sets or returns the maximum value. the Value Changed event is fired, and the Action assigned to the Slider is
It is of type Float. performed. In this example, we defined the action with a method called
updateProgress(). In this method, we take the current value of the Slider from
setValue(Float, animated: Bool)—This method sets the selected
value as the value specified by the first argument and moves the
the value property and assign it to the progress property of the UIProgressView @IBOutlet weak var slider: UISlider!
object to update the progress bar. override func viewDidLoad() {
super.viewDidLoad()
slider.minimumValue = 0.0
Do It Yourself: Delete the elements in the scene. Add a Progress slider.maximumValue = 10.0
View and a Slider from the Library (as illustrated in Figure 5-78). slider.value = 0.0
Create an Outlet for the progress bar called progress and an Action for slider.minimumTrackTintColor = UIColor.systemRed
the Slider called updateProgress(). Update the view controller with the slider.maximumTrackTintColor = UIColor.systemYellow
}
code in Listing 5-49. The progress bar should change according to the @IBAction func updateProgress(_ sender: UISlider) {
position of the Slider. let currentValue = sender.value / 10
progress.progress = currentValue
}
As we have seen above, we can specify different values for the Slider from }

the Attributes Inspector panel and from code, but the class also provides
properties to customize the Slider, change colors, and add images on the
In this example, we connect the Slider to the view controller with an Outlet
sides to represent minimum and maximum values.
called slider to modify its values and appearance. Now the Slider is
presented with different colors and runs from 0.0 to 10.0. The rest of the
minimumTrackTintColor—This property sets or returns the color
application works the same but notice that we couldn’t assign the value of
of the bar on the left side of the indicator. It is an optional of type
the Slider directly to the Progress View, as we did before. By default, the
UIColor.
Progress View and the Slider work with the same range of values (0.0 to
maximumTrackTintColor—This property sets or returns the color 1.0). If we change the range for the Slider, we must adapt the number
of the bar on the right side of the indicator. It is an optional of type returned by this element to the Progress View range to be able to display
UIColor. the correct level of progress. In our example, the value of the Slider goes
from 0 to 10, so we divide the value of the value property by 10 to get a
minimumValueImage—This property sets or returns the image
number between 0.0 and 1.0 that we can assign to the progress bar.
that represents the minimum value. It is an optional of type UIImage.
maximumValueImage—This property sets or returns the image
that represents the maximum value. It is an optional of type UIImage.

Listing 5-50: Working with custom values



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var progress: UIProgressView!

Stepper The class also includes properties to configure the control’s behavior and
appearance.

autorepeat—This property sets or returns a Boolean value that
A Stepper includes two buttons with the minus and plus symbols that users
indicates if the value is incremented automatically. If it is set to true,
can press to increment or decrement a value. It is defined by the UIStepper
class. We can create a Stepper from code with the UIView initializer or add it the value of the Stepper is incremented repeatedly while the user
to a scene from the option in the Library. holds the button down.
isContinuous—This property sets or returns a Boolean value that
UIStepper(frame: CGRect)—This initializer creates a UIStepper indicates if the updated values are reported during the user
object with the position and size specified by the argument. interaction or only when the user interaction ends.
wraps—This property sets or returns a Boolean value that
Figure 5-79: Stepper in the Library determines how the value is processed when it reaches the minimum
or maximum values allowed. If true, when the value goes beyond the
limit the opposite limit is assigned to it and the value keeps
 decrementing or incrementing in a loop. On the other hand, when the
property is false, the value stops decrementing or incrementing when a
A Stepper is set with values by default. The minimum is set to 0.0, the limit is reached.
maximum to 100.0, the initial value to 0.0, and the incremental value to tintColor—This property sets or returns the color of the control.
1.0, but we can adapt the control to our needs from the Attributes
Inspector panel or the class properties.
A Stepper works the same way as other controls we have already
introduced. When the user presses a button, the control fires a Value
value—This property sets or returns the current value of the Stepper. Changed event, so we can define an Action to respond to this event and
minimumValue—This property sets or returns the minimum value. process the new value or perform a task. For instance, we can show the
It is of type Double. current value with a label, as in the following example.
maximumValue—This property sets or returns the maximum value.
Figure 5-80: Interface to test the Stepper
It is of type Double.
stepValue—This property sets or returns the number by which the
current value of the Stepper will be incremented or decremented. It is
of type Double.
connected to the view controller with an Action called increment(), so every
time the user presses a button, the new value is assigned to the label and
displayed on the screen.

Do It Yourself: Delete the elements in the scene. Add a label and a


Stepper to the scene. Double-click the label to change its text to
"0.0", as illustrated in Figure 5-80. Create an Outlet for the label
 called counterLabel and an Action for the Stepper called increment().
Update the ViewController class with the code in Listing 5-51. Run the
The values of the Stepper’s properties may be modified from the Attribute application. The value in the label should be incremented or
inspector panel or from code. This example illustrates how to set the
decremented every time you press the buttons of the Stepper.
values from the view controller.

Listing 5-51: Customizing the values of a Stepper



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var stepper: UIStepper!
@IBOutlet weak var counterLabel: UILabel!

override func viewDidLoad() {


super.viewDidLoad()
stepper.value = 0.0
stepper.minimumValue = 0.0
stepper.maximumValue = 10.0
stepper.stepValue = 1.0
}
@IBAction func increment(_ sender: UIStepper) {
counterLabel.text = String(sender.value)
}
}

The view controller defines an Outlet for the Stepper and change its values
when the view is loaded. We set an initial value of 0, a minimum value of 0,
a maximum value of 10, and declare the step with a value of 1.0, so the
user can select a value from 0 to 10 in steps of 1. The Stepper was also

Text Field right, justified, and natural.


 clearsOnBeginEditing—This property sets or returns a Boolean
value that determines whether the text inside the field should be
A Text Field is a rectangular box on the screen that activates the keyboard erased when the user begins editing.
to let the user insert characters or paste text in it. It is defined by the borderStyle—This property sets or returns the value of the style for
UITextField class. We can create a Text Field from code with the UIView the border. It is an enumeration called BorderStyle included in the
initializer or add it to a scene from the option in the Library. UITextField class. The values available are none (no border), line (single
line around the field), bezel (single line with shadows), and roundedRect
UITextField(frame: CGRect)—This initializer creates an object of (lines with round corners).
the UITextField class with a position and size determined by the frame background—This property sets or returns the background image. It
argument. is an optional value of type UIImage. The image replaces the border
defined by the borderStyle property.
Figure 5-81: Text Field option in the Library
The UITextField class also conforms to a protocol called UITextInputTraits that
defines a set of properties to configure the input produced by the
keyboard and the keyboard itself.

autocapitalizationType—This property sets or returns a value that
The following are the most frequently used properties included in the
determines when the Shift key will be automatically pressed to insert
class.
uppercase letters. It is an enumeration of type
UITextAutocapitalizationType with the values none (Shift is never activated),
text—This property sets or returns the text inside the field. It is an
words (Shift is activated to capitalize each word), sentences (Shift is
optional of type String.
activated to capitalize sentences), and allCharacters (Shift is always
placeholder—This property sets or returns the message that is activated).
displayed while the field is empty. It is an optional of type String.
autocorrectionType—This property sets or returns a value that
font—This property sets or returns the font used by the Text Field to determines whether auto-correction is enabled while typing. It is an
show the text. It is an optional of type UIFont. enumeration of type UITextAutocorrectionType with the values default, no,
textColor—This property sets or returns the color of the text. It is an and yes.
optional of type UIColor. spellCheckingType—This property sets or returns a value that
textAlignment—This property sets or returns the text’s alignment. It determines whether spell checking is enabled while typing. It is an
is an enumeration of type NSTextAlignment with the values left, center,
enumeration of type UITextSpellCheckingType with the values default, no,
and yes.
keyboardType—This property sets or returns a value that
determines what type of keyboard to open. It is an enumeration of
type UIKeyboardType with the values default, asciiCapable,
numbersAndPunctuation, URL, numberPad, phonePad, namePhonePad,
emailAddress, decimalPad, twitter, and webSearch.

returnKeyType—This property sets or returns a value that


determines the title of the Return key. It is an enumeration of type
UIReturnKeyType with the values default, go, google, join, next, route, search, 
send, yahoo, done, and emergencyCall.
As we do with any other element in the interface, to access the text
isSecureTextEntry—This property sets or returns a Boolean value
inserted by the user and modify the characteristics of the Text Field from
that determines whether the text should be hidden (used to hide
code, we must create an Outlet. The interface in Figure 5-82 requires two
passwords). The value by default is false.
Outlets, one for the label and another for the Text Field, and an Action for
the Save button to process the text.
As with any other element, we can set up the Text Field from the
Attributes Inspector panel and later change some aspects and read its
Listing 5-52: Processing the user’s input
values from code. To illustrate this process, we are going to implement the

following interface, which includes a label, a Text Field with the
import UIKit
placeholder "Insert title here", and a button to process the text inserted by
the user. class ViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var titleInput: UITextField!
Figure 5-82: Interface to receive the user’s input
@IBAction func changeTitle(_ sender: UIButton) {
if titleInput.text != "" {
titleLabel.text = titleInput.text
titleInput.text = ""
}
}
}

The code in Listing 5-52 does not introduce anything new, but thanks to
the Text Field we are now able to process input from the user. When the

user taps on the Save button, the changeTitle() method is executed and the spaces, tabulations, or characters that represent new lines. The following
label’s text is replaced with the text inserted by the user. The first thing we example implements the whitespaces property to remove the space
do is to confirm that the Text Field is not empty comparing its text property characters at the beginning and the end of the string inserted by the user.
with an empty string (titleInput.text != ""). When there is text to process, the Notice that this time we take advantage of the isEmpty property defined by
value of this property is assigned to the label’s text property (titleLabel.text = the String structure to check if the string is empty or not. This produces the
titleInput.text), effectively replacing the text on the screen with the text same result as before but can simplify our code in some circumstances.
inserted by the user. Finally, the Text Field is cleared to receive new input.
Listing 5-53: Trimming text
Do It Yourself: Delete the elements in the scene. Add a label with 
the word "Title", a Text Field with the placeholder "Insert title here", import UIKit
and a button called "Save" (Figure 5-82). Create Outlets for the label
class ViewController: UIViewController {
and the Text Field called titleLabel and titleInput. Create an Action for @IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var titleInput: UITextField!
the Save button called changeTitle(). Update the view controller with
the code in Listing 5-52. Run the application, insert a text, and press @IBAction func changeTitle(_ sender: UIButton) {
var text = titleInput.text!
the Save button. You should see the text replacing the title. text = text.trimmingCharacters(in: .whitespaces)

In the example of Listing 5-52, we compare the value inserted by the user if !text.isEmpty {
titleLabel.text = text
with an empty string. If the string is not empty, we process it. But titleInput.text = ""
sometimes users unintentionally add space characters at the beginning or }
}
the end of the string. The String structure includes a convenient method }
defined specifically to remove these unwanted characters. 

trimmingCharacters(in: CharacterSet)—This method erases the The UITextField class, as well as other classes that create visual controls on
characters indicated by the in argument at the beginning and end of the screen, is a subclass of the UIControl class and the UIControl class is a
the string and returns a string with the result. The argument is a subclass of the UIView class, therefore, UITextField objects have access to all
their properties and methods, such as the backgroundColor property to
CharacterSet structure with the set of characters we want to erase.
change the background color or the isEnabled property to enable or disable
the field. The following example modifies the isEnabled property to disable
The trimmingCharacters() method takes a structure of type CharacterSet to
the Text Field after the first input.
know what to remove. This structure creates sets of characters for
searching operations. The definition includes a few type properties to
Listing 5-54: Disabling the Text Field
create structures with standard sets. The most useful are whitespaces and 
whitespacesAndNewlines, which return sets that contain invisible characters, like
import UIKit content.
class ViewController: UIViewController { textFieldShouldEndEditing(UITextField)—This method is called
@IBOutlet weak var titleLabel: UILabel!
by the Text Field on the delegate object when the user tries to switch
@IBOutlet weak var titleInput: UITextField!
focus to another element. The method must return a Boolean value
@IBAction func changeTitle(_ sender: UIButton) { that indicates if edition should stop or not.
var text = titleInput.text!
text = text.trimmingCharacters(in: .whitespaces) textFieldDidEndEditing(UITextField)—This method is called by
if !text.isEmpty {
the Text Field on the delegate object after the element loses focus.
titleLabel.text = text
titleInput.text = "" textField(UITextField, shouldChangeCharactersIn: NSRange,
titleInput.placeholder = "Text Field Disabled"
titleInput.isEnabled = false replacementString: String)—This method is called by the Text Field
} on the delegate object every time the user inserts or deletes a
}
}
character or a string of characters. The shouldChangeCharactersIn
 argument determines the range of characters on the field affected by
the operation, and the replacementString argument is the new
The changeTitle() method in Listing 5-54 assigns the new text to the label as character or string of characters inserted by the user. The method
before, but now it also modifies the isEnabled property to disable the field must return a Boolean value that indicates whether the text should be
and shows a new placeholder to report the situation to the user. Once the replaced or not.
Text Field is disabled, the user cannot type anymore. textFieldShouldClear(UITextField)—This method is called by the
These properties provide some level of customization, but they are not Text Field on the delegate object to know if the Text Field should be
enough to control the user’s input. Sometimes we need to determine what cleared when the Clear button is pressed. The method must return a
the user is allowed to insert or how the Text Field should respond. For this Boolean value that indicates whether the Text Field should be cleared
purpose, the UITextField class includes the delegate property to designate a or not.
delegate to the Text Field. The UITextField object calls methods on this
delegate object to report the state of the process. The object assigned as
textFieldShouldReturn(UITextField)—This method is called by the
Text Field on the delegate object when the user taps the Return key
the Text Field’s delegate must conform to the UITextFieldDelegate protocol,
on the keyboard. The method must return a Boolean value that
which includes the following methods.
indicates if the action should be considered or not.
textFieldShouldBeginEditing(UITextField)—This method is called
Usually, the object assigned as the Text Field’s delegate is the same view
by the Text Field on the delegate object to know if edition should be
controller that controls the scene to which the Text Field belongs. In the
allowed. The method must return a Boolean value that indicates if
following example, the ViewController object is assigned to the delegate
edition is allowed or not.
property of the Text Field and then the textFieldShouldBeginEditing() method is
textFieldDidBeginEditing(UITextField)—This method is called by implemented. This method is another way to enable or disable a Text Field.
the Text Field on the delegate object when the user begins editing its

The UITextField object calls it as soon as the user taps on the Text Field. If object and report changes in the state of the Text Field by calling the
the value returned by the method is false, the user is not allowed to insert corresponding methods. For example, when the user taps on the Text Field
text. to start writing on it, the UITextField object calls the textFieldShouldBeginEditing()
method to know if the user is allowed to type on it (in our example, we
Listing 5-55: Assigning a delegate to the Text Field and declaring its return false, indicating that the user is not allowed).
methods The methods of the UITextFieldDelegate protocol are optional, which means
 we can implement only the ones we need. If the method does not exist
import UIKit inside the delegate, the Text Field uses the values by default. For example,
if we do not declare the textFieldShouldBeginEditing() method, as we did in
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var titleLabel: UILabel! Listing 5-55, the Text Field considers the value to be true and allows the
@IBOutlet weak var titleInput: UITextField! user to edit the field.
override func viewDidLoad() { The textFieldShouldBeginEditing() method is a very simple method, but we can
super.viewDidLoad() declare others to control more aspects of the interaction between the user
titleInput.delegate = self
and the Text Field. For example, if we want to control what the user is
}
@IBAction func changeTitle(_ sender: UIButton) { allowed to type in the field, we can implement the
titleLabel.text = titleInput.text textField(shouldChangeCharactersIn:, replacementString:) method. Every time the
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { user types a character, the UITextField object calls this method to know
return false whether it should include the entry or not. Implementing this method, we
}
}
can process the input while it is entered.

Listing 5-56: Determining the number of characters allowed
The first thing we need to do in our view controller is to declare that the 
class conforms to the UITextFieldDelegate protocol, so the UITextField object import UIKit
knows that this object implements the protocol methods. This is done by
class ViewController: UIViewController, UITextFieldDelegate {
adding the name of the protocol after the name of the class, separated by @IBOutlet weak var titleLabel: UILabel!
a comma (see Protocols and Delegates in Chapter 3). Next, we must @IBOutlet weak var titleInput: UITextField!
declare the view controller object as the delegate of the Text Field, so the override func viewDidLoad() {
UITextField object knows what object implements the protocol methods. super.viewDidLoad()
This is part of the Text Field’s configuration, so it has to be done before titleInput.delegate = self
}
anything else, and that is why we assign the self keyword to the delegate @IBAction func changeTitle(_ sender: UIButton) {
property inside the viewDidLoad() method (self is a reference to the object titleLabel.text = titleInput.text
}
that the code belongs to, in this case, the ViewController object). Now the func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
UITextField object can use the delegate property to access our view controller replacementString string: String) -> Bool {
if let text = textField.text { if (Int(string) != nil && textField.text != "0") || string == "" {
let total = text.count + string.count - range.length return true
if total > 10 { }
return false return false
} }
} 
return true
}
} To determine if the user inserted a number we use the Int() initializer. If the
 value returned by Int() is not nil, it means that the string parameter contains
a number. But there are a few other things we must contemplate to create
The textField(shouldChangeCharactersIn:, replacementString:) method receives three this filter. The first is that, although the number 0 is a valid integer number,
values from the Text Field: a reference to the UITextField object, an NSRange an integer number of two digits or more cannot begin with 0. Therefore,
value that represents the range of characters in the Text Field that are we ought to validate the entry only if the current value in the Text Field is
going to be replaced by the new entry, and a String value with the new not equal to 0. These two values, the value created by the initializer and
entry. Based on these values, we must determine if the entry should be the current value of the text property, determine two of the conditions we
included in the field or not and return a Boolean value to communicate our need to check to validate the entry, but there is one more. When the user
decision. For example, a common task is to limit the number of characters presses a key that does not produce any character, such as the Delete key,
allowed in the field. We add the number of characters currently stored in the value assigned to the string parameter is an empty string and therefore
the text property to the number of characters the user wants to introduce we also need to check this value to determine if the entry is valid or not.
minus the length of the range and return false if the value exceeds the Inside the method of Listing 5-57, we create a logical sequence to check all
maximum allowed. This procedure is followed in the example of Listing 5- these conditions. If string contains a number and the current value of the
56. We count the characters in the text property and the string parameter, text property is not 0, or the value of string is an empty string, the logical
add both values, subtract the length of the array, which represents the sequence evaluates to true, and the entry is validated.
number of characters currently selected by the user, and finally store the
total in the total constant. The value of total is then compared to 10 (the Do It Yourself: Replace the textField(shouldChangeCharactersIn:,
maximum number of characters we want to allow in the Text Field for this method of Listing 5-56 by the method of Listing 5-
replacementString:)
example) and the value false is returned if total is greater than this number, 57. Run the application. You should only be allowed to type
rejecting the new entry.
numbers. If you type the number 0 first, the system will not let you
The possibilities of control are limitless. We can filter the input in any way
insert any additional number.
we want. For instance, we could let the user insert only numbers.

Another useful method of the UITextFieldDelegate protocol is


Listing 5-57: Allowing only numbers
textFieldShouldReturn(). This method is called every time the user presses the

Return key on the keyboard. As an example, we can use it to perform the
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool { same action as the Save button.

Listing 5-59: Highlighting the Text Field


Listing 5-58: Responding to the keyboard 
 func textFieldDidBeginEditing(_ textField: UITextField) {
textField.backgroundColor = UIColor.systemGray4
import UIKit
}
class ViewController: UIViewController, UITextFieldDelegate { 
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var titleInput: UITextField! If our interface includes two or more Text Fields, we can take advantage of
override func viewDidLoad() { other methods like textFieldDidEndEditing(). Instead of performing a task when
super.viewDidLoad() the Text Field is tapped, with this method we can do it when the Text Field
titleInput.delegate = self
loses focus (the user taps somewhere else). For example, if we expand our
}
@IBAction func changeTitle(_ sender: UIButton) { interface to include another Text Field, we can highlight only the one
assignTitle() currently selected. Figure 5-83 shows what an interface like this looks like.
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
assignTitle() Figure 5-83: Interface with two Text Fields
return true
}
func assignTitle() {
titleLabel.text = titleInput.text
}
}

Because we are performing the same action for both, the Save button and
the Return key, we process the text in a new method called assignTitle() and
then call this method every time the button or the key are pressed. When
the user presses the Return key, the textFieldShouldReturn() method is called.
This method executes the assignTitle() method to replace the label’s text and
then returns the value true to tell the system to implement the default 
behavior for the Return key.
Other methods of the UITextFieldDelegate protocol are called when the editing In this example, the original Text Field is used to introduce the title and the
begins or ends. For example, we can implement the textFieldDidBeginEditing() new Text Field is used for the subtitle, so now the Save button must assign
method to highlight the active Text Field. The following example changes both values to the label.
the Text Field’s background color to a light gray when the user taps on it to
start typing. Listing 5-60: Highlighting two Text Fields

import UIKit Outlet called subtitleInput. Update the ViewController class with the code
class ViewController: UIViewController, UITextFieldDelegate { in Listing 5-60. Run the application and tap on one Text Field and
@IBOutlet weak var titleLabel: UILabel! then the other to see how their background colors change.
@IBOutlet weak var titleInput: UITextField!
@IBOutlet weak var subtitleInput: UITextField!
Working with two or more Text Fields in the same view and connected to
override func viewDidLoad() {
super.viewDidLoad() the same delegate presents a challenge. The methods called on the
titleInput.delegate = self delegate are always the same, no matter which Text Field performed the
subtitleInput.delegate = self
}
call. This is the reason why all the methods defined in the UITextFieldDelegate
@IBAction func changeTitle(_ sender: UIButton) { protocol include a parameter with a reference to the UITextField object that
if titleInput.text != "" && subtitleInput.text != "" { performed the call. We can use this reference to modify the object, as we
titleLabel.text = titleInput.text! + " - " + subtitleInput.text!
titleInput.text = "" did before, but also to identify the Text Field we are working with. One
subtitleInput.text = "" simple way to do this is to assign a value to the object’s tag property. A
}
}
different value is assigned to the tag property of every Text Field from the
func textFieldDidBeginEditing(_ textField: UITextField) { Attributes Inspector panel and then the property is read from code to
textField.backgroundColor = UIColor.systemGray4 know which Text Field called the method. For example, the following view
}
func textFieldDidEndEditing(_ textField: UITextField) { controller implements the textField(shouldChangeCharactersIn:, replacementString:)
textField.backgroundColor = UIColor.white method to establish different limits to the number of characters the user
}
} can type on each field.

Listing 5-61: Identifying Text Fields
This view controller includes an Outlet called subtitleInput for the new Text 
Field and assigns itself as the delegate for both Text Fields. Every time the import UIKit
user types on any of the Text Fields, the protocol methods are called in our
class ViewController: UIViewController, UITextFieldDelegate {
view controller. In this example, we have implemented the @IBOutlet weak var titleLabel: UILabel!
textFieldDidBeginEditing() and the textFieldDidEndEditing() methods to change the @IBOutlet weak var titleInput: UITextField!
background colors. When the user taps on one of the Text Fields to start @IBOutlet weak var subtitleInput: UITextField!

typing, its background becomes gray, as it did in the previous example, but override func viewDidLoad() {
now when the user taps on the other Text Field, the first one loses focus super.viewDidLoad()
titleInput.delegate = self
and it calls the textFieldDidEndEdition() method. From this method, we change subtitleInput.delegate = self
the Text Field’s background color back to white to reflect the new state. }
@IBAction func changeTitle(_ sender: UIButton) {
if titleInput.text != "" && subtitleInput.text != "" {
Do It Yourself: Add a new Text Field to the interface, as shown in titleLabel.text = titleInput.text! + " - " + subtitleInput.text!
titleInput.text = ""
Figure 5-83. Connect this element to the view controller with an

subtitleInput.text = "" Text View


}
} 
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
var maximum = 0 A Text View is a scrollable view that can be used to display and insert long
if textField.tag == 1 {
maximum = 10 texts. If the size of the text surpasses the size of the view, the view allows
} else { the user to scroll it. It is defined by the UITextView class. We can create a
maximum = 15
}
Text Field from code with the UIView initializer or add it to a scene from the
if let text = textField.text { option in the Library.
let total = text.count + string.count - range.length
if total > maximum {
return false UITextView(frame: CGRect)—This initializer creates an object of
} the UITextView class with a position and size determined by the frame
}
return true argument.
}
}
Figure 5-84: Text View option in the Library

This example assumes that the values of the Text Fields’ tag properties were
defined as 1 and 2, respectively. The textField(shouldChangeCharactersIn:,

replacementString:) method checks this value to know which Text Field is
making the call (textField.tag == 1), sets the value of the maximum variable, and
The following are the most frequently used properties included in the
then compares the result with the total amount of characters allowed to
UITextView class.
validate the entry (total > maximum).
text—This property sets or returns the text in the view. It is an
Do It Yourself: Select each Text Field in the Storyboard and assign
optional of type String.
the corresponding values to their tag properties from the Attributes
Inspector panel (1 for the title and 2 for the subtitle). Update the attributedText—This property sets or returns an NSAttributedString
object with the Text View's attributed text.
ViewController class with the code in Listing 5-61. Run the application.
The title should be limited to 10 characters and the subtitle to 15. font—This property sets or returns the font used by the Text View to
display the text. It is an optional of type UIFont.
textColor—This property sets or returns the color of the text. It is an
optional of type UIColor.
textAlignment—This property sets or returns the text’s alignment. It textViewDidEndEditing(UITextView)—This method is called by
is an enumeration of type NSTextAlignment with the values left, center, the Text View on the delegate object when the element loses focus.
right, justified, and natural. textView(UITextView, shouldChangeTextIn: NSRange,
isEditable—This property sets or returns a Boolean value that replacementText: String)—This method is called by the Text View
determines whether the user is allowed to edit the text inside the Text on the delegate object every time the user inserts or deletes a
View or not. character or a string of characters. The shouldChangeTextIn argument
isSelectable—This property sets or returns a Boolean value that determines the range of characters on the view affected by the
determines whether the user is allowed to select text inside the Text operation, and the replacementText argument is the new character or
View or not. string inserted by the user. The method returns a Boolean value that
indicates whether the text should be replaced or not.
selectedRange—This property sets or returns the range of the text
selected inside the Text View. It is of type NSRange. textViewDidChange(UITextView)—This method is called by the
Text View on the delegate object when the user changes the text or
scrollRangeToVisible(NSRange)—This method scrolls the view to
any of its attributes.
show on the screen the text that corresponds to the range specified
by the argument. textViewDidChangeSelection(UITextView)—This method is
called by the Text View on the delegate object when the user selects
The UITextView class can also designate a delegate to report the state of the text.
process. The object assigned as the Text View’s delegate must conform to
the UITextViewDelegate protocol, which includes the following methods. As always, we can provide the values for all the properties from the
Attributes Inspector panel, including the text to show inside the view.
textViewShouldBeginEditing(UITextView)—This method is called When a Text View is selected, the panel includes an option at the top to
by the Text View on the delegate object to know if edition should be indicate the type of text we want to assign to the view: Plain or Attributed.
allowed. The method returns a Boolean value that indicates if edition If the Attributed option is selected, additional buttons appear to assign the
is allowed or not. attributes.
textViewDidBeginEditing(UITextView)—This method is called by
Figure 5-85: Attributed text for the Text View
the Text View on the delegate object when the user begins editing its
content.
textViewShouldEndEditing(UITextView)—This method is called
by the Text View on the delegate object when the user tries to switch
focus to another element. The method returns a Boolean value that
indicates if edition should stop or not.



The view controller for this interface is simple, we just need an Outlet to
From the Attributes Inspector panel, we can change every aspect of the reference the Text View and an Action for the button. Because we want to
text or parts of it, but this only sets the Text View’s initial value. If we want modify the attributes of the text (change the color of the characters
to modify the view’s content or replace it when the app is running, we selected by the user) we must convert the NSAttributedString object returned
must do it from code. The UITextView class offers two properties to access by the attributedText property of the UITextView object into an AttributedString
the object’s content: text and attributedText. With the text property, we can set structure with the structure's initializer defined for this purpose, as
plain text with common attributes, and with the attributeText property we explained before (see Listing 5-22).
can assign attributed text to the view.
The following example introduces a new interface with a button and a Text Listing 5-62: Adding attributes to the attributed text of a Text View
View. The purpose of the button is to assign new attributes to the 
characters selected by the user. import UIKit

Figure 5-86: Interface to test Text Views class ViewController: UIViewController {


@IBOutlet weak var message: UITextView!

@IBAction func selection(_ sender: UIButton) {


if let text = message.attributedText {
if let attrText = try? AttributedString(text, including: \.uiKit) {
if let newRange = Range(message.selectedRange, in: attrText) {
var newText = attrText
newText[newRange].foregroundColor = .systemRed
message.attributedText = NSAttributedString(newText)
}
}
} class ViewController: UIViewController, UITextViewDelegate {
} @IBOutlet weak var message: UITextView!
} override func viewDidLoad() {
 super.viewDidLoad()
message.delegate = self
}
After we convert the NSAttributedString object into an AttributedString func textViewDidChange(_ textView: UITextView) {
structure, we get the range of indexes that determines the characters if let text = message.attributedText {
if let attrText = try? AttributedString(text, including: \.uiKit) {
selected by the user and process the string. The range is provided by the let currentPos = message.selectedRange
Text View’s selectedRange property, but this is an NSRange object and we need var newText = attrText
a Range structure with Index values, so we use the Range(in:) initializer to let chars = newText.characters

convert it to the right type (see Ranges in Chapter 4). Once the attributed for pos in chars.indices {
string and the range are ready, the procedure is the same implemented in let distance = chars.distance(from: pos, to: chars.endIndex)
let endPos = chars.index(pos, offsetBy: min(distance, 4))
previous examples. We add the attribute to the range of characters and
then assign the resulting string back to the Text View. let word = String(chars[pos..<endPos])
if word == "John" {
newText[pos..<endPos].foregroundColor = .systemBlue
Do It Yourself: Delete the elements in the scene. Add a button }
called Change Color and a Text View to get an interface like Figure 5- }
message.attributedText = NSAttributedString(newText)
86. Connect the Text View to the view controller with an Outlet message.selectedRange = currentPos
}
called message and the button with an Action called selection(). Update }
the ViewController class with the code in Listing 5-62. Run the }
}
application, double click a word to select it, and press the Change 
Color button to change its color to red.
The code in Listing 5-63 works with the same interface used before, but
Of course, the characters selected by the user are not the only one we can this time we assign the view controller as the Text View’s delegate and
modify. Any range of characters can be changed at any moment. For implement the textViewDidChange() method. This method is called by the
example, we could search for specific words in the text and change the UITextView object every time the content of the Text View changes. This way,
attributes for only those characters. In the following example, we search when the user types or erases a character, our code is aware of it and can
for the string "John" and change the color of these characters to blue as process the new value.
the user types in the Text View. There are several steps we must take to find the words we want to modify.
After turning the NSAttributedString object returned by the Text View into an
Listing 5-63: Adding attributes to a range of characters AttributedString structure, we store the value of the selectedRange property. This
 is to capture to cursor's current position and set it back in the same
import UIKit position after the new attributed string is assigned to the Text View. Next,

we assign the structure to a variable to be able to modify its attributes, get Keyboard
the collection of characters from the characters property, and finally iterate

through the indexes of this collection to get the index of each character in
the string and process it. Each cycle of the loop gets the index of a
iOS has a particular way to control the keyboard. It does not consider the
character in the string. To find a word, we must create a range between
keyboard as a tool, but rather a way to detect events produced by the user,
this index and the one that indicates the end of the word. In this case, the
and therefore it keeps it on screen for as long as the element that made it
word we are looking for is "John" and therefore the last index is 4 positions
appear is still capable of processing the input. To dismiss the keyboard, we
after the initial index. First, we get the distance between the initial position
must tell the system that the element is no longer able to process the
and the end of the string to make sure the indexes are not out of range.
events, and this is done by altering the chain of response.
Then, we get the index of the last character with the index(offsetBy:) method.
When an event occurs, such as a tap on the screen, the system determines
And finally, we use these values to get the range of characters in that
which element is going to process it. The position of the elements on the
position and compare it to the word "John". If the strings match, we assign
screen does not always reflects this responsibility. To figure out which
the foregroundColor attribute to that portion of the string, changing the
element is responsible to process an event, the system considers the
characters color to blue.
elements hierarchy and creates a virtual chain in which some elements in
certain positions, called Responders, receive the event, and then decide
Do It Yourself: Update the ViewController class with the code in
whether to process it or deliver it to the next element in the chain. Some
Listing 5-63 and run the application. Tap on the text to activate the
events, like key events from the keyboard, are sent to specific elements
cursor and write the text “John”. You should see the color of the called First Responders.
word change to blue. First Responders are designated by the system or our code. This is what the
system does when the user taps on a Text Field or a Text View; it declares
the element as the First Responder, so all the events are first sent to it. And
this is also how the system manages the keyboard. The keyboard opens
when an element that is capable of handling key events becomes the First
Responder, and it is closed when the element ceases to be the First
Responder (it resigns as First Responder).
The UIKit framework defines the UIResponder class to manage Responders
and respond to events. The following are its most frequently used
properties and methods.

isFirstResponder—This property returns a Boolean value that


indicates whether the element is the current designated First
Responder or not.
becomeFirstResponder()—This method designates the element as
class ViewController: UIViewController {
the First Responder. @IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var titleInput: UITextField!
resignFirstResponder()—This method notifies the element that it
has to resign its condition of First Responder. @IBAction func changeTitle(_ sender: UIButton) {
if titleInput.text != "" {
touchesBegan([UITouch], with: UIEvent?)—This method is called titleLabel.text = titleInput.text
by the system in responder objects (views and view controllers) when titleInput.text = ""
titleInput.resignFirstResponder()
the user starts touching the screen. } else {
touchesMoved([UITouch], with: UIEvent?)—This method is titleInput.becomeFirstResponder()
}
called by the system in responder objects (views and view controllers) }
when the finger touching the screen moves. }

touchesEnded([UITouch], with: UIEvent?)—This method is called
by the system in responder objects (views and view controllers) when The view controller in Listing 5-64 includes Outlets for the label and the
the user stops touching the screen. Text Field, and an Action for the button. When the user taps the Save
touchesCancelled([UITouch], with: UIEvent?)—This method is button, the changeTitle() method checks the content of the Text Field as
called by the system in responder objects (views and view controllers) before. If something was inserted into the field, the method assigns that
when a touch event is cancelled by the system. value to the label and then executes the resignFirstResponder() method to
force the Text Field to resign its condition as First Responder. When the
The UIResponder class is a basic class that most of the UIKit classes inherit system detects that there is no First Responder that can process key
from. Because of this, all the elements created from UIKit classes have events, it closes the keyboard. On the other hand, if the changeTitle() method
access to its properties and methods and are capable of handling events. If cannot find any text inside the Text Field, it executes the
we want to open the keyboard, we can call the becomeFirstResponder() method becomeFirstResponder() method, turning the Text Field into the First Responder
on an element capable of handling key events (which produces the same and compelling the system to open the keyboard.
effect as the user tapping on the element). Closing the keyboard is as
simple as calling the resignFirstResponder() on the element currently assigned Do It Yourself: Create an interface like the one presented in Figure
as First Responder. 5-82. Connect the label, the Text Field and the button with the
The following example shows how to implement these two methods. corresponding Outlets and Action. Update the ViewController class
This view controller assumes that we have an interface with a Text Field, a with the code in Listing 5-64. Run the application and click the Save
label, and a button, as the one presented in Figure 5-82. button. The keyboard will pop up and the Text Field will show the
cursor. Write something in the Text Field and press the Save button
Listing 5-64: Opening and closing the keyboard from code
again. The keyboard should be dismissed.

import UIKit

IMPORTANT: The simulator includes the same keyboard of a real The touchesBegan() method implemented in Listing 5-65 is called on the view
device, but it can also work with the computer’s keyboard. To select controller when the user touches the screen. We override this method in
one keyboard or another, open the I/O menu at the top of the our view controller to perform custom tasks. In this case, we use it to call
screen, go to Keyboard and select the option of your preference. the resignFirstResponder() method on the Text Field to dismiss the keyboard.
As a result, the keyboard is opened when the user taps on the Text Field
The becomeFirstResponder() method is not always necessary. Usually, the user and closed when the user taps somewhere else (except on the Save
taps on the element when it needs to type something on it and this action button, because the button provides its own response to the event).
automatically makes the element the First Responder, compelling the Calling the resignFirstResponder() method on an element only removes the
system to open the keyboard. But the execution of the resignFirstResponder() status of First Responder for that element. If we have several elements that
method is required every time we want to dismiss the keyboard because can become First Responders, such as the multiple Text Fields we had in
there is no automatic action to do it. In our previous example, we called the example of Figure 5-83, we must call the method on each one of them.
this method when the Save button was pressed, but a more intuitive To simplify our work, the UIView class includes a special method that looks
interface will also close the keyboard when the user taps somewhere else at the view and its subviews to find the current First Responder and ask the
on the screen. These types of events are detected by the event-handling element to resign.
methods included in the UIResponder class, as shown next.
endEditing(Bool)—This method looks for the First Responder in a
Listing 5-65: Dismissing the keyboard when the user touches the screen view and its subviews and asks it to resign its condition. The argument
 indicates whether the element should be forced to resign or not.
import UIKit
The following example implements the same view controller of Listing 5-65
class ViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel! but instead of calling the resignFirstResponder() method on each Text Field it
@IBOutlet weak var titleInput: UITextField! calls the endEditing() method on the main view (the view property). This
@IBAction func changeTitle(_ sender: UIButton) { method finds the current First Responder inside the view and forces it to
if titleInput.text != "" { resign its condition. It is the same process, but now we do not have to call
titleLabel.text = titleInput.text
the resignFirstResponder() method for every Text Field in the view.
titleInput.text = ""
}
} Listing 5-66: Finding the First Responder
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event) 
titleInput.resignFirstResponder() import UIKit
}
} class ViewController: UIViewController {
 @IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var titleInput: UITextField!

@IBAction func changeTitle(_ sender: UIButton) {


if titleInput.text != "" { Picker View
titleLabel.text = titleInput.text
titleInput.text = "" 
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { A Picker View is a view that allows the user to select a value from a list. The
super.touchesBegan(touches, with: event)
view.endEditing(true) values are organized in rows and components (columns). Each component
} represents a list of values, and each row represents an individual value.
}

The components scroll independently.
Pickers are defined by the UIPickerView class. We can create a Picker View
from code with the UIView initializer or add it to a scene from the option in
the Library.

UIPickerView(frame: CGRect)—This initializer creates an object of


the UIPickerView class with a position and size determined by the frame
argument.

Figure 5-87: Picker View option in the Library

To identify each row and component, the picker creates indexes starting
from 0. The first value of a row will be at index 0, the second at index 1,
and so on. The items in a picker are organized this way so we can associate
it with arrays in our code and easily present and retrieve values. The class
defines the following properties and methods to interact with the picker.

numberOfComponents—This property returns a value of type Int


that represents the number of components in the picker.
numberOfRows(inComponent: Int)—This method returns a value
of type Int that represents the number of rows in a component. The
argument indicates the index of the component we want to access.

selectedRow(inComponent: Int)—This method returns a value of the forComponent argument. The method must return an
type Int that represents the index of the selected row in the NSAttributedString object.
component specified by the inComponent argument. pickerView(UIPickerView, didSelectRow: Int, inComponent:
reloadAllComponents()—This method forces the picker to update Int)—This method is called by the UIPickerView object on the delegate
the values of its components. when the user selects a row (moves the wheel to get a new value at
reloadComponent(Int)—This method forces the picker to update the center). The didSelectRow and inComponent arguments contain
the values of a component. The argument indicates the index of the the indexes of the selected row and component.
component we want to update.
On the other hand, the object assigned to the dataSource property must
selectRow(Int, inComponent: Int, animated: Bool)—This
conform to the UIPickerViewDataSource protocol, which includes the following
method selects the row with the index specified by the first argument
methods.
in the component specified by the inComponent argument. The
animated argument determines whether the selection should be
numberOfComponents(in: UIPickerView)—This method is called
animated or not. The method rotates the wheel until the selected row
by the UIPickerView object on the data source delegate to know the
is positioned at the center.
number of components to display. The method must return an Int
value with the number of components we want.
The UIPickerView class also defines two properties, delegate and dataSource, to
assign delegate objects that will configure the picker and provide the pickerView(UIPickerView, numberOfRowsInComponent: Int)
values for the rows. The object assigned to the delegate property must —This method is called by the UIPickerView object on the data source
conform to the UIPickerViewDelegate protocol, which includes the following delegate to know how many rows to display in a component. The
methods. numberOfRowsInComponent argument specifies the index of the
component (the method is called for every component in the picker).
pickerView(UIPickerView, titleForRow: Int, forComponent:
Int)—This method is called by the UIPickerView object on the delegate Picker Views are usually combined with other controllers to process the
when it needs a value for the row indicated by the titleForRow values selected by the user. The interface in Figure 5-88, below, includes a
argument in the component indicated by the forComponent picker that we are going to use to show a list of years and a label that is
argument. The method must return a string with the value for the going to display the year selected by the user.
row.
Figure 5-88: Interface to test a Picker View
pickerView(UIPickerView, attributedTitleForRow: Int,
forComponent: Int)—This method is called by the UIPickerView
object on the delegate when it needs a value for the row indicated by
the attributedTitleForRow argument in the component indicated by
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) ->
Int {
return years.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component:
Int) -> String? {
return years[row]
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component:
Int) {
let year = years[row]
showYear.text = "The Year is: \(year)"
 }
}

When the picker is added to a scene from the Library, Xcode shows it with
some values by default, as illustrated in Figure 5-88, but the real values are This view controller defines Outlets for the label and the Picker View, and
provided by the delegate object assigned to the dataSource property. This is a an array called years to store the values we want to assign to the picker. In
normal delegate, but because its purpose is to provide data it is usually the viewDidLoad() method, we use the Outlet to declare the view controller
called data source (hence the name of the property). As always, the as the picker’s delegate and data source, and also initialize the years array
delegates could be any objects we want, but it is common practice to with a list of values.
declare the view controller in charge of the picker to be the delegate as After the properties are initialized, we must configure the picker and load
well as the data source, as in the following example. the data from the delegate and data source methods. We first implement
the numberOfComponents() method to tell the picker that we want one
Listing 5-67: Providing values for the picker component to show the list of years (one column). The next method,
 pickerView(numberOfRowsInComponent:), performs a similar function, but this
import UIKit time for the rows. Inside this method, we count how many values are in
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { the years array and return that number to indicate how many rows we need
@IBOutlet weak var showYear: UILabel! to show them all. Next is the pickerView(titleForRow:, forComponent:) method,
@IBOutlet weak var pickerYears: UIPickerView!
var years: [String]! called by the picker to get the value for each row. The first call will be for
the row at index 0, then for the row at index 1, and so on. Using these
override func viewDidLoad() {
indexes, we get the corresponding value from the years array and return it.
super.viewDidLoad()
pickerYears.delegate = self Notice that this method returns a String value. If we have an array of
pickerYears.dataSource = self integers, as in this case, before returning each value we must convert it to
years = ["1944", "1945", "1946", "1947", "1948", "1949", "1950"]
} a String using string interpolation or the String() initializer (e.g., String(number)).

The last method, pickerView(didSelectRow:, inComponent:), is called by the


picker when the user rotates the wheel and selects a new value. The index
of the selected row is reported by the method through its didSelectRow
argument. Using this index, we can retrieve the value from the array and
assign it to the label.

Do It Yourself: Delete the elements in the scene. Add a Picker View


and a label to the scene, as shown in Figure 5-88. Connect the label
and the picker to the view controller with Outlets called showYear and
pickerYears, respectively. Update the ViewController class with the code
in Listing 5-67 and run the application. Every time you rotate the
wheel, the selected year should be shown on the label.

The pickerView(didSelectRow:, inComponent:) method allows us to get the value as
soon as it is selected, but we can also read the selected value any time we In this example, the method for the action replaces the
want with the selectedRow() method. To test it, we can add a button to the pickerView(didSelectRow:, inComponent:) method, but they could work together if
interface and an Action to the view controller to change the value of the necessary.
label every time the button is pressed.
Listing 5-68: Reading the selected value
Figure 5-89: Interface to get the value currently selected 
import UIKit

class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {


@IBOutlet weak var showYear: UILabel!
@IBOutlet weak var pickerYears: UIPickerView!
var years: [String]!

override func viewDidLoad() {


super.viewDidLoad()
pickerYears.delegate = self
pickerYears.dataSource = self
years = ["1944", "1945", "1946", "1947", "1948", "1949", "1950"]
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> array with the firstIndex(of:) method, and then we select the row using the
Int {
return years.count index returned by this method. The value of the animated argument was
} set to false because we want the picker to show the right value as soon as it
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component:
Int) -> String? {
is displayed, but we can take advantage of this argument to create a
return years[row] smooth transition when the selection is done after the picker was already
} shown to the user.
@IBAction func getYear(_ sender: UIButton) {
let row = pickerYears.selectedRow(inComponent: 0)
showYear.text = "The Year is: \(years[row])" Do It Yourself: Replace the viewDidLoad() method in the previous
}
} example by the one in Listing 5-69 and run the app. The selected
 value in the picker should be "1945".

In the getYear() method of Listing 5-68, we call the selectedRow() method to get With a few modifications, we can easily create a picker with more than one
the index of the selected row for the component at index 0 (we only have component. The following example includes two components to let the
one component in our picker) and then use the value of the row constant to user select a year and a city.
retrieve the year from the array and assign it to the label.
Besides the user rotating the wheel, there is also a method that allows us Listing 5-70: Creating a picker with multiple components
to select a value from code. This is particularly useful when we want the 
picker to suggest a value to the user. The following example initializes the
import UIKit
picker with the year 1945.
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource {
@IBOutlet weak var showYear: UILabel!
Listing 5-69: Selecting the initial value @IBOutlet weak var pickerYears: UIPickerView!
 var cities: [String]!
var years: [String]!
override func viewDidLoad() {
super.viewDidLoad()
override func viewDidLoad() {
pickerYears.delegate = self
super.viewDidLoad()
pickerYears.dataSource = self
pickerYears.delegate = self
years = ["1944", "1945", "1946", "1947", "1948", "1949", "1950"]
pickerYears.dataSource = self
cities = ["Mountain View", "Sunnyvale", "Cupertino", "Santa Clara"]
if let index = years.firstIndex(of: "1945") {
years = ["1944", "1945", "1946", "1947", "1948", "1949", "1950"]
pickerYears.selectRow(index, inComponent: 0, animated: false)
}
}
func numberOfComponents(in pickerView: UIPickerView) -> Int {
}
return 2

}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) ->
Because we want to initialize the picker with this value, we call the method Int {
if component == 0 {
from the viewDidLoad() method. First, we search for the value "1945" in the return cities.count

} else {
return years.count
}
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component:
Int) -> String? {
if component == 0 {
return cities[row]
} else {
return years[row]
}
}
@IBAction func getYear(_ sender: UIButton) {
let rowCity = pickerYears.selectedRow(inComponent: 0)
let rowYear = pickerYears.selectedRow(inComponent: 1)
showYear.text = "The Year is: \(years[rowYear]) in \(cities[rowCity])"
}
}


This view controller provides values for two components. The years array for
the years and the cities array for the cities. To tell the picker that we want
two components, the value 2 was returned from the numberOfComponents()
method. From this point on, we must consider the component every time
we interact with the picker. If the index of the component is 0, we are
working with the cities array, and when the index is 1, we are working with
the years array. The getYear() method was also expanded to read the selected
value from each component and assign them to the label. The result is
shown below.

Figure 5-90: Picker with two components


Date Picker minimumDate—This property sets or returns the minimum date the
picker can show. It is a conditional of type Date.

date—This property sets or returns the selected date. It is of type
The Date Picker was designed to simplify the creation of pickers for dates Date.

and times. It is defined by the UIDatePicker class (a subclass of UIPickerView). minuteInterval—This property sets or returns an integer value that
With this class, we can create objects that show dates, dates and times, determines the interval at which the minutes should be shown (1 by
and a countdown timer. This picker can be created from code, using the default).
UIView initializer or by dragging the option from the Library. countDownDuration—This property sets or returns a value of type
that indicates the seconds from which the timer starts
TimeInterval
UIDatePicker(frame: CGRect)—This initializer creates an object of counting down.
the UIDatePicker class with a position and size determined by the frame
setDate(Date, animated: Bool)—This method selects a new date.
argument.
The first argument is a Date value that specifies the date we want to
select, and the animated argument is a Boolean value that
Figure 5-91: Date Picker option in the Library
determines whether we want the selection to be animated or not.

The class also includes three properties to determine the type of data to
 show. If the properties are not defined, they take the default values set on
the device.
The class provides the following properties to configure the picker.
calendar—This property sets or returns the calendar to use by the
datePickerMode—This property sets or returns the picker’s mode. picker. It is an optional of type Calendar.
It is an enumeration called Mode with the values time, date, dateAndTime, locale—This property sets or returns the locale (regional information)
and countDownTimer. to use by the picker. It is an optional of type Locale.
preferredDatePickerStyle—This property sets or returns the style timeZone—This property sets or returns the time zone of the date
of the picker. It is an enumeration called UIDatePickerStyle with the shown by the picker. It is an optional of type TimeZone.
values automatic (selected according to the platform), compact (label that
expands into a calendar), inline (editable field or calendar, depending There are three types of Date Pickers: Compact, Wheels, and Inline, as
on the platform), and wheels (standard wheel picker). shown below.
maximumDate—This property sets or returns the maximum date
the picker can show. It is a conditional of type Date. Figure 5-92: Date Picker styles

we are going to create an interface like the one used for Picker Views, as
shown below.

Figure 5-94: Interface to test Date Pickers


By default, the Style is set to Automatic, which means that the system is
going to select the appropriate style according to the space available on
the interface. If we want the keep a consistent style, we can set a specific
value from code or from the Attributes Inspector panel. For instance, in the
following example we have selected the Wheel style, to show the picker as
a wheel, the Date mode to show only dates, and set a minimum and a
maximum date.

Figure 5-93: Date Picker properties

The view controller for this interface only needs to include two Outlets for
the picker and the label, and an Action for the button.

Listing 5-71: Displaying the selected date



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var picker: UIDatePicker!
@IBOutlet weak var showDate: UILabel!

@IBAction func getDate(_ sender: UIButton) {



let selectedDate = picker.date
let format = selectedDate.formatted(date: .abbreviated, time: .omitted)
A Date Picker does not work with delegates, we interact with the picker showDate.text = "Date: \(format)"
}
directly through its properties and methods. To illustrate how this works, }

Picker and change the Style to Wheel and the mode to Date from the
Getting the date selected on the picker is as simple as reading the date Attributes Inspector panel. Connect the label and the picker to
property. This property is of type Date, which means we can format the date Outlets called showDate and picker, respectively. Connect the button to
and show it to the user (see Dates in Chapter 4). In this example, we call an Action called getDate(). Update the ViewController class with the code
the formatted() method with the abbreviated and omitted values to generate a
in Listing 5-71. You can also add the viewDidLoad() method of Listing 5-
string with a date and no time.
72 to define an initial value. Run the application, select a date, and
Of course, we also declare an initial value for Date Pickers. The value may
press the button to assign it to the label. Stop the app. Select the
be assigned directly to the date property or by calling the setDate() method.
The only difference is that with the method we have the chance to animate picker on the scene and try different styles from the Attributes
the selection. The viewDidLoad() method below initializes the picker with the Inspector panel to see how they look and work.
date 08-13-2010.

Listing 5-72: Setting the initial date



override func viewDidLoad() {
super.viewDidLoad()
let calendar = Calendar.current
var components = DateComponents()
components.year = 2010
components.month = 8
components.day = 13

if let newDate = calendar.date(from: components) {


picker.date = newDate
}
}

The code in Listing 5-72 implements a DateComponents structure and the date()
method of the Calendar structure to create a date. Because the date()
method returns an optional, we unwrap it before assigning it to the date
property. Once we add the viewDidLoad() method to the previous example,
the initial values of the picker will be August 13, 2010.

Do It Yourself: Delete the elements in the scene. Add a Date Picker,


a label, and a button to the scene (Figure 5-94). Select the Date

5.7 Images One solution to this problem is to scale up a small image in devices with
higher resolution or scale down a big image in devices with lower

resolution. For example, we can expand an image of 300 x 400 pixels to
600 x 800 pixels and make it look like the same size in a screen with a scale
The screens of Apple devices have different resolutions and scales. In some
of 2x (a space of 300 x 400 points represents 600 x 800 pixels at this scale),
devices, one point represents one pixel and in others more. At this
or we could start with an image of 600 x 800 pixels and reduce it to 300 x
moment, three scales have been defined: 1x, 2x, and 3x. The 1x scale
400 pixels for devices with half the scale. One way or another, we have a
defines one point as one pixel, the 2x scale defines 1 point as a square of 2
problem. If we expand a small image to fill the screen, it loses quality, and
pixels, and the 3x scale defines one point as a square of three pixels. For
if we reduce it, it occupies unnecessary space in memory because the
this reason, every time we want to show images in our interface, we must
image is never shown in its original resolution. Fortunately, there is a more
consider the conversion between pixels and points. For example, if we have
efficient solution. It requires us to include in our project three versions of
an image of 300 pixels wide and 400 pixels tall, in a device with a scale of
the same image, one for every scale. Considering the image of our
1x the image will almost fill the screen, but in a device with a scale of 2x
example, we will need one picture of the husky in a size of 300 x 400 pixels
the image will look half its size. The image is occupying the same space,
for devices with a scale of 1x, another of 600 x 800 pixels for devices with a
300 by 400 pixels, but because of the higher resolution these pixels
scale of 2x, and a third one of 900 x 1200 for devices with a scale of 3x.
represents a smaller area on the screen in devices with scales of 2x or 3x,
Now, the images can be shown in the same size and with the same quality
as shown below.
no matter the device or the scale.

Figure 5-95: Same image in devices with different scale


Figure 5-96: Different images for specific scales



Providing the same image in different resolutions solves the problem but
introduces some complications. We must create three versions of the same
image and then select which one is going to be shown depending on the
scale of the device. To help us select the right image, Apple systems detect
the scale that corresponds to the image by reading a suffix on the file’s
name. What we need to do is to provide three files with the same name
but with suffixes that determine the scale for which they were designed.
The file containing the image for the 1x scale (300 x 400 pixels in our
example) only requires the name and the extension (e.g., husky.png), the
name of the file with the image for the 2x scale (600 x 800) must include
the suffix @2x (e.g., [email protected]), and the name of the file with the
image for the 3x scale (900 x 1200) must include the suffix @3x (e.g.,
[email protected]). Every time the interface requires an image, the system
reads the suffixes and loads the one corresponding to the scale of the
screen.

IMPORTANT: There is a useful app in the App Store for Mac When we drop the files into our Xcode project, a window asks for
computers called Prepo that can take an image of a scale of 3x and
information about the destination and the target. If we want the files to be
reduce it to create the versions for the rest of the scales. It can also
copied to the project’s folder (recommended), we must activate the option
help you generate the icons for your app.
Copy items if needed, as shown in Figure 5-98 below. We also must indicate
that the files are going to be added to the target created for our project
There are two ways to incorporate images into our project. One is to drag
selecting the target in the Add to targets option (the project used in this
the files to the Navigator Area and the second alternative is to use a tool
example was called Test).
called Assets Catalog. The first option is simple. We must create the images
for the resolutions we want to provide and then drag them from Finder to
Figure 5-98: Options to add files to the project
the Navigator Area, as shown in Figure 5-97.

Figure 5-97: Dragging files from Finder to our Xcode’s project

Assets Catalog
After these options are selected and the Finish button is pressed, Xcode

includes the files with the rest of the files in our project.
Although we could include all the images for our application by adding the
files to the project, as we did in Figure 5-97, this is not practical.
Sometimes dozens, if not hundreds, of images are necessary to create an
app. No matter how we name or classify these files, it will still be very
difficult to keep them organized. For this purpose, the UIKit framework
defines a class called UIImageAsset that creates a container called Assets
Catalog to organize the project’s resources, including images, icons, colors,
and more. Templates include an Assets Catalog for our app and Xcode
offers a simple interface to edit its content. To open the interface, we must
click on the file in the Navigator Area called Assets.xcassets. Figure 5-99
shows what we see in the Editor Area.

Figure 5-99: Empty Assets Catalog

The interface includes two columns: the column on the left presents a list
with the sets of resources available and the column on the right displays
the content of the selected set (e.g., the three images for each scale). The
list also includes two predefined sets called AccentColor and AppIcon to
define the default tint color for the controls and the app's icons, as we will
see later.
New sets can be added from the Add New Asset option in the Editor menu
at the top of the screen or by pressing the + button at the bottom of the
left column. For instance, Figure 5-100 shows what we see when we create
a new Image set.

Figure 5-100: New set of images
Once the images are loaded to the catalog, the set is ready to use. We do
not need to copy the files to the project’s folder anymore or anything like
it. All we need to do is to load the image from our app using the name
defined for the set ("husky" in our example). But this process is still a little
bit tedious. An easy way to create a new set is to drag the three images for
the set to the Assets Catalog and let Xcode define everything for us. For
 instance, we can drag and drop the files door.png, [email protected], and
[email protected] and Xcode takes care of creating the set, assigning each
The name of the set is the name we must use to get the image from code. image to the corresponding scale, and give it the name "door".
Xcode calls the new set Image but we can click on it and press Enter to
change it any time we want. By default, a set for an image includes the Figure 5-102: New door set
three versions of the image, one for each scale. Once the set is created, we
can drag the files to the corresponding squares. For example, the file
husky.png from our previous example goes inside the 1x square, the file
[email protected] goes inside the 2x square, and the file [email protected] goes
inside the 3x square. Figure 5-101, below, shows the Editor Area after the
images are dragged from Finder to the Assets Catalog and the name of the
set is changed to "husky". 

Figure 5-101: Husky set is ready Besides the possibility of including a version of the image for every scale,
we can also add versions for different devices and parameters. When a set
is selected, the Attributes Inspector panel on the right shows a list of
properties, including Devices, Appearances, and more. By default, the set is
shown in any device (Universal) and for any type of interface, but we can
change it by modifying the values of these attributes. For example, if we

select the iPad option from the list of devices (Figure 5-103, number 1), Image
new placeholders are added to the interface (Figure 5-103, number 2).

Figure 5-103: Set for specific devices
Because the purpose of images is not only to be presented on the screen
but also to be used in the construction of patterns and customized
controls, the UIKit framework provides a class called UIImage to load
images. The UIImage class can load one image at a time but includes
multiple initializers to load the image from a variety of sources. The
following are the most frequently used.

UIImage(named: String)—This initializer creates an object that


contains the image from the file specified by the named argument.
 The argument is a string with the name of the file, or the image set in
the Assets Catalog.
After the images are added to the set, the system selects the appropriate
UIImage(data: Data, scale: CGFloat)—This initializer creates an
version according to the scale, the device, and the characteristics of the
object that contains an image generated from the data provided by
interface.
the data argument and with an associated scale specified by the scale
argument. If the scale is 1, the last argument can be ignored.
UIImage(cgImage: CGImage, scale: CGFloat, orientation:
Orientation)—This initializer creates an object that contains an
image generated from a CGImage object and with a scale and
orientation defined by the arguments. The CGImage class is defined by
the Core Graphics framework to store a low-level representation of an
image, and the orientation argument is an enumeration with the
values up, down, left, right, upMirrored, downMirrored, leftMirrored, and
rightMirrored.

The UIImage class includes a few type properties to return objects with
standard images. The properties available are add, remove, actions, checkmark,
and strokedCheckmark. There are also properties and methods to get
information about the image and process it. The following are the most Listing 5-73: Adding a background pattern to the main view
frequently used. 
import UIKit
size—This property returns a CGSize value with the size of the image. class ViewController: UIViewController {
scale—This property returns a CGFloat value with the scale of the override func viewDidLoad() {
super.viewDidLoad()
image. if let mypattern = UIImage(named: "oranges") {
view.backgroundColor = UIColor(patternImage: mypattern)
imageOrientation—This property returns a value that identifies the }
orientation of the image. It is an enumeration called Orientation }
}
included in the UIImage class. The values available are up, down, left, right, 
upMirrored, downMirrored, leftMirrored, and rightMirrored.

cgImage—This property returns the image in the Core Graphic The UIImage() initializer implemented in Listing 5-73 looks for an image in
the Assets Catalog with the name "oranges" and returns a UIImage object if
format. It is of type CGImage, a Core Graphic data type.
the image is found and successfully loaded, or the value nil otherwise. In
pngData()—This method converts the image into raw data in the this example, we use the image to create a background pattern with a
PNG format and returns a Data structure with it. UIColor object. When a UIColor object is initialized with an image, the object
jpegData(compressionQuality: CGFloat)—This method converts takes that image and fills the area with it, as shown below.
the image into raw data in the JPEG format and returns a Data
structure with it. The compressionQuality argument is a value Figure 5-104: Main view with a background pattern
between 0.0 and 1.0 that determines the level of compression.

Working with images from code is very simple. We must create the UIImage
object with the source we want and then apply it to the elements on the
interface or show it on the screen. Many elements include properties that
take a UIImage object. For instance, the UIColor class can take a UIImage
object to create a pattern that later can be applied to the background of a
view. In the following example, we load an image called oranges.png and
apply it to the background of the scene's main view. The example assumes
that we have created a set in the Assets Catalog called oranges with the
files oranges.png, [email protected], and [email protected] (the files are 
available on our website).

Do It Yourself: Create a new project. Download the three


oranges.png files from our website or provide your own. Drag the
files to the Assets Catalog to create a set called oranges. Update the
ViewController class with the code in Listing 5-73. Run the application.
You should see something like Figure 5-104.

Some elements, such as buttons, use UIImage objects to customize their


appearance. Once the images are added to the project, we can assign them
to the element from the Attributes Inspector panel. The following example
shows how to create a custom button using an image.

Figure 5-105: Button with a custom image

 Custom buttons can present more images depending on their state. If, for
example, we want the button to show a different image when it is pressed,
The button is added to the scene from the Library as always, but with a we must add it for the Highlighted state, as shown in Figure 5-107 below
Custom Type, a Default Style, no title, and the image we want to show, as (number 1). Once this option is selected, the Image field is emptied to let
illustrated in Figure 5-106 below. (This example uses the images us pick the new image for that state. This example uses the images
buttonnormal.png, [email protected], and [email protected], buttondown.png, [email protected], and [email protected],
available on our website.) available on our website.

Figure 5-106: Configuring the button from the Attributes Inspector panel Figure 5-107: Adding an image for the Highlighted state
Attributes Inspector panel, and set the Type as Custom, the style as
Default, erase the title, and select the buttonnormal image for the
Default state and the buttondown image for the Highlighted state
(Figures 5-106 and 5-107). Run the application. When you press the
button, the image should change, as illustrated in Figure 5-108
(right).

When the app is executed, the UIImage objects that represent the images
we have added for the button are created and shown on the screen. The
interface displays the button with the image defined for the Default state
and replaces it by the second image when the button is pressed.

Figure 5-108: Custom button with a different image when pressed (right)

Do It Yourself: Download the three buttonnormal.png files and the


three buttondown.png files from our website and add them to the
Assets Catalog of the project created for the previous example. Add
a button from the Library to the scene. Select the button, go to the

SF Symbols UIImage(systemName: String, withConfiguration:



Configuration)—This initializer creates an object that contains an SF
symbol. The systemName argument is the name of the symbol, and
Although we can implement our own images, when working with buttons, the withConfiguration argument is the symbol's configuration,
markers, or indicators, it is better to use SF Symbols. SF Symbols are including font, size, weight, and more.
predefined symbols provided by Apple to represent functionality, such as
the image of an envelope to represent the possibility to send emails or a The withConfiguration argument is optional. For instance, if we only
magnifying glass representing the possibility to perform a search. The provide the symbol's name, the system creates an SF Symbol with values
graphics are identified with a string, are scalable, and come in different by default. In the following example, we change the configuration of a
versions, which simplifies the integration with the rest of the interface. Filled button on the interface to include an image with an SF Symbol.
There are plenty of symbols available to cover every need our application
may have. To help us find the symbols we want, Apple provides a free Listing 5-74: Implementing SF Symbols
application called SF Symbols that we can download from 
developer.apple.com (Develop/Downloads/Release/Applications). The app import UIKit
includes options to search for symbols by name or category. class ViewController: UIViewController {
@IBOutlet weak var myButton: UIButton!
Figure 5-109: SF Symbols app
override func viewDidLoad() {
super.viewDidLoad()
myButton.configurationUpdateHandler = { button in
var current = button.configuration
current?.title = "Add Comment"
current?.image = UIImage(systemName: "trash")
current?.imagePadding = 15
button.configuration = current
}
}
}

This view controller changes the button’s configuration as we did before,



but this time we include an image with the symbol of a trash can.

We have introduced SF Symbols before in this chapter. They are


Figure 5-110: Button with an SF Symbol
implemented by buttons to show images along with the title, but we can
create them ourselves with the UIImage initializer.
current?.imagePadding = 15
button.configuration = current
}
}
}

This example loads a symbol with the name “trash.circle”, which represents
the image of a trash can surrounded by a circle.

Figure 5-111: Button with a variant of an SF Symbol
Do It Yourself: Delete the elements in the scene. Add a Filled
button from the Library. Connect the button to the ViewController class
with an Outlet called mybutton. Update the class with the code in
Listing 5-74 and run the application. You should see a button like the
one in Figure 5-110.

SF Symbols come in different versions. For instance, the symbol with the
name “trash” implemented in our example has a version with a circle 
around the can, another with a line across, and more. These are called
variants. Symbol’s names follow a pattern we can use to identify a variant. SF Symbols adopt the size of the text or the elements they are applied to,
First, we write the base name, and then specify the variants with dot but when the symbol is used independently, there is no reference for the
notation. system to set the size or the appearance. In cases like this, or when we
want to specify a different configuration than the one set by the system,
Listing 5-75: Selecting a variant of an SF Symbols we can provide a configuration object. These objects are created from the
 SymbolConfiguration class. The class includes multiple initializers. The

import UIKit following are the most frequently used.

class ViewController: UIViewController {


@IBOutlet weak var myButton: UIButton! SymbolConfiguration(pointSize: CGFloat, weight:
SymbolWeight)—This initializer creates an object with the
override func viewDidLoad() {
super.viewDidLoad() configuration defined by the arguments. The pointSize argument
myButton.configurationUpdateHandler = { button in determines the font size the symbol uses as reference, and the weight
var current = button.configuration
current?.title = "Add Comment" argument determines the font’s weight. This last argument is a
current?.image = UIImage(systemName: "trash.circle") SymbolWeight enumeration with the values unspecified, ultraLight, thin, light,

regular, medium, semibold, bold, heavy,


and black. The enumeration also }

includes the fontWeight() method to declare a weight.
SymbolConfiguration(textStyle: TextStyle, scale: SymbolScale) Figure 5-112: SF Symbol with a custom size
—This initializer creates an object with the configuration defined by
the arguments. The textStyle argument is a Dynamic Font (e.g., body,
headline, etc.), and the scale argument specifies the symbol's scale. This
is an enumeration with the values unspecified, small, medium, and large.
SymbolConfiguration(font: UIFont, scale: SymbolScale)—This
initializer creates an object with the configuration defined by the
arguments. The font argument is the font the symbol uses as

reference, and the scale argument specifies the symbol's scale. This is
an enumeration with the values unspecified, small, medium, and large.
In additions to the base color, some symbols can include up to three more
colors. And these colors can also be defined with a configuration object.
The purpose of the configuration object is to define the characteristics of
The SymbolConfiguration class includes the following initializers for this
the font the system will use as reference to draw the symbol. For instance,
purpose.
we can specify a size of 30 points.
SymbolConfiguration(paletteColors: [UIColor])—This initializer
Listing 5-76: Configuring an SF Symbols
creates a configuration object to set the colors to the colors specified

import UIKit
by the paletteColors argument.
SymbolConfiguration(hierarchicalColor: UIColor)—This
class ViewController: UIViewController {
@IBOutlet weak var myButton: UIButton! initializer creates a configuration object that defines all the symbol's
override func viewDidLoad() {
colors from a base color specified by the hierarchicalColor argument.
super.viewDidLoad()
let config = UIImage.SymbolConfiguration(pointSize: 30, weight: .regular) These initializers return a configuration object that defines the symbol's
myButton.configurationUpdateHandler = { button in colors, but they do not provide an alternative to define other attributes,
var current = button.configuration such as the size or weight. To combine the configuration object created by
current?.title = "Add Comment"
current?.image = UIImage(systemName: "trash", withConfiguration: config)
any of the previous initializers with an object created by the color
current?.imagePadding = 15 initializers, the SymbolConfiguration class includes the following method.
button.configuration = current
}
}
applying(SymbolConfiguration)—This method combines the
configuration object with the object specified by the argument.

Not all symbols are multicolor. In fact, most symbols are monochrome. To
know which symbols can handle multiple colors, we can use the SF
Symbols app. Once we find the symbol we want, the configuration is done
as before. The following example illustrate how to configure the bell.circle
symbol with two colors and a custom size. 

Listing 5-77: Defining the colors of an SF Symbols



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var myButton: UIButton!

override func viewDidLoad() {


super.viewDidLoad()
let configSize = UIImage.SymbolConfiguration(pointSize: 35, weight: .regular)
let configColors = UIImage.SymbolConfiguration(paletteColors: [.systemRed, .white])
let config = configSize.applying(configColors)

myButton.configurationUpdateHandler = { button in
var current = button.configuration
current?.title = "Add Comment"
current?.image = UIImage(systemName: "bell.circle.fill", withConfiguration: config)
current?.imagePadding = 15
button.configuration = current
}
}
}

This view controller defines a configuration object to determine the


symbol's size, a second configuration object to determine the colors, and
then combines both configurations with the applying() method and assigns
the resulting configuration to the UIImage object as we did before.

Figure 5-113: SF Symbol with custom colors

Image View
Figure 5-115: Adding an Image View to a scene

UIImage objects may be added to views, buttons, or other elements to


create customized controls, but if we just want to show the image on the
screen, we must create an Image View. This view is defined by the
UIImageView class, which includes the following initializer.

UIImageView(image: UIImage?)—This initializer creates a


UIImageView object with the image provided by the image argument.

UIImageView objects can also be incorporated to the Storyboard from the


option in the Library.

Figure 5-114: Image View option in the Library

 To assign an image to the view, we can modify its image property from code
or select it from the Attributes Inspector panel, as shown below.
The class includes properties to retrieve the image and configure the view.
Figure 5-116: Assigning an image to the Image View
image—This property sets or returns the image. It is an optional of
type UIImage.
isUserInteractionEnabled—This property sets or returns a Boolean
value that determines if the Image View responds to user events. By
default, it is set to false.

Image Views are views that contain images and therefore they are added
to the interface as any other view. The following interface includes a
button at the top and an Image View at the bottom to display the selected 
image.
Image Views have their own size, independent of the size of the image they Image Views, as any other element, may be connected to the view
contain. When we load an image, we must tell the system how to controller with Outlets and then modified from code. For example, we
accommodate the image inside the view. This is done by modifying the could replace the image when the button is pressed.
value of the contentMode property provided by the UIView class. This property
can take several values, with the most useful being Scale to Fill, Aspect Fit, Listing 5-78: Replacing the picture when the button is pressed
and Aspect Fill. Scale to Fill expands or contracts the image to fit the size of 
the view, Aspect Fit scales the image to fit the size of the view, maintaining import UIKit
its aspect ratio, and Aspect Fill scales the image to always fill the view but
class ViewController: UIViewController {
also keeping its aspect ratio, which means some parts of the image may lay @IBOutlet weak var pictureView: UIImageView!
outside the boundaries of the view and, depending on the configuration, var door: UIImage!
they may be hidden. Figure 5-117 shows what the image of the husky looks override func viewDidLoad() {
like when the view is set to different modes. super.viewDidLoad()
door = UIImage(named: "door")
}
Figure 5-117: Different modes applied to the same Image View @IBAction func changePicture(_ sender: UIButton) {
pictureView.image = door
}
}

The view controller includes an Outlet to the Image View and a property to
store the image. When the view is loaded, we create the UIImage object
with the image of the door and assign it to the door property. Once we have
this object, we can assign it to the image property of the Image View any
 time we want to show the image on the screen. In fact, the image can be
replaced at any time. For instance, we can store two UIImage objects with
Do It Yourself: Delete the elements in the scene. Add a Filled different images and show one or the other depending on a condition, as
button and an Image View from the Library. Expand the views to in the following example.
resemble the interface in Figure 5-115. Download the husky.png,
[email protected] and [email protected] files from our website and add Listing 5-79: Switching pictures
them to the Assets Catalog. Select the Image View and assign the 
husky.png image to it from the Attributes Inspector panel (Figure 5- import UIKit
116). Try different Content Mode settings to see how the image is class ViewController: UIViewController {
affected. @IBOutlet weak var pictureView: UIImageView!
var currentActive: Bool = false
var husky: UIImage!

var door: UIImage! Do It Yourself: Add the images of the husky and the door to the
override func viewDidLoad() { Assets Catalog (the images are available on our website). Connect
super.viewDidLoad() the Image View to the ViewController class with an Outlet called
husky = UIImage(named: "husky")
door = UIImage(named: "door") pictureView and the button with an Action called changePicture(). Update
}
@IBAction func changePicture(_ sender: UIButton) {
the class with the code in Listing 5-79 and run the application. Press
if currentActive { the button to change the picture on the screen.
pictureView.image = husky
} else {
pictureView.image = door
}
currentActive.toggle()
}
}

The Image View is the same as before, but now we have two UIImage
objects, one with the image of the husky and another with the image of
the door. When the button is pressed, we check the value of the
currentActive property and assign one of the images to the view. At the end,
we toggled the value of this property so next time the other image is
assigned instead. The result is shown next. Every time the button is
pressed, the image on the screen changes.

Figure 5-118: Different images for the same Image View


Processing Images image is a very common requirement. For instance, the resolution of
photographs taken by the camera is too high for database storage. In cases

like this, we can use the preparingThumbnail(of:) method to reduce the size of
the image or create thumbnails. As an example, we can use it to get a
To save storage space and bandwidth, images are compressed in formats
thumbnail of our husky image.
such as Jpeg and PNG. When it is time to display the image on the screen,
the system decompresses the data and resizes the image to fit inside the
Listing 5-80: Reducing the size of an image
view according to the content mode set for the Image View. Letting the

system take care of these tasks for us is fine when we are working with just
import UIKit
a few images or displaying one image at a time, but it can affect
performance when multiple images are shown on the screen at the same class ViewController: UIViewController {
@IBOutlet weak var pictureView: UIImageView!
time, as it happens when working with Table Views and Collection Views var husky: UIImage!
(see Chapter 10 and Chapter 11). To ensure that performance is not
affected by these processes, we should always provide images of a size that override func viewDidLoad() {
super.viewDidLoad()
matches their screen size and decompress them beforehand. For this husky = UIImage(named: "husky")
purpose, the UIImage class includes the following methods. }
@IBAction func changePicture(_ sender: UIButton) {
let thumbnail = husky.preparingThumbnail(of: CGSize(width: 100, height: 100))
preparingThumbnail(of: CGSize)—This method returns a new pictureView.contentMode = .top
pictureView.image = thumbnail
image created from the original and with a size determined by the of }
argument. }

prepareThumbnail(of: CGSize, completionHandler: Closure)—
This method creates a new image from the original and calls a closure This example works with the same interface introduced in Figure 5-115.
with the result. The of argument determines the size of the image. The image is connected to the pictureView outlet, and the button to the
The closure receives a UIImage object with the result. changePicture() action. When the button is pressed, we call the
preparingThumbnail(of:) method to create an image of a maximum of 100
preparingForDisplay()—This method decompresses the original
points. Notice that we change the view's content mode to top. This is to
image and returns a new one ready to be shown on the screen.
make sure that the image is shown on the screen in its original size. (By
prepareForDisplay(completionHandler: Closure)—This method default, the content mode of an Image View is set to Aspect Fit, which
decompress the original image and calls a closure when the process is causes the image to stretch to take up the available space.)
over. The closure receives a UIImage object with the result.
Figure 5-119: Thumbnail
The process of preparing an image to be shown on the screen is often only
necessary in high performance applications but reducing the size of an

Visual Effects

Modern versions of iOS make extensive use of transparency and Apple


encourages developers to adopt these types of design in their own apps.
The purpose is to create the illusion of depth so that the user feels that the
content extends beyond the screen. One of the tricks used to achieve this
effect is to make images appear blurry. The UIKit framework includes the
UIVisualEffectView class to achieve this effect. The class includes an initializer
with an argument to define the effect.

UIVisualEffectView(effect: UIVisualEffect?)—This initializer


creates a UIVisualEffectView object with the effect set by the effect
argument.

 The framework currently offers two subclasses of the UIVisualEffect class to


define the effect: UIBlurEffect and UIVibrancyEffect. The following are the
IMPORTANT: The UIImage class also defines two asynchronous initializers.
methods to perform these tasks: byPreparingForDisplay() and
byPreparingThumbnail(ofSize: CGSize). The advantage of asynchronous UIBlurEffect(style: Style)—This initializer creates a UIBlurEffect
methods is that the system can perform other tasks while the object with the style defined by the style argument. The argument is
methods finish processing. We will study concurrency in Chapter 14. an enumeration with multiple values, including the following to create
For an example on how to implement these methods, see Chapter an effect for light and dark appearances: systemUltraThinMaterial,
16, Listing 16-13. systemThinMaterial, systemMaterial, systemThickMaterial, and
systemChromeMaterial.

UIVibrancyEffect(blurEffect: UIBlurEffect)—This initializer


creates a UIVibrancyEffect object to add vibrancy to a blur effect. The
vibrancy is used to highlight a view inside a blur view.

The Library includes two options to add an effect view with a blur effect to
a scene.
and another Image View on top of the Visual Effect View to get the
Figure 5-120: Visual Effect View options in the Library interface in Figure 5-121, right.

These options create a view that we must place over the views we want to
affect. For instance, we can add a Visual Effect View with Blur over a
background image. We add the Image View for the background first, then
the effect view, and finally the rest of the interface, as shown below.

Figure 5-121: Interface with a Visual Effect View

The Attributes Inspector panel includes the option to select the style. The
value defines different levels of opacity. In our example, we used the Ultra
Thin Material effect.

Do It Yourself: Download the background.jpg image from our


website and add it to the Assets Catalog. Add an Image View to the
scene and assign the background image to it. Expand the Image View
to fill the main view (Figure 5-121, left). Add a Visual Effect View with
Blur on top of the Image View (Figure 5-121, center). Select the Blur
Style for this view from the Attributes Inspector panel. Add a button

Dark Appearance There are two types of resources we need to adapt to these appearances:
colors and images. Although we could perform these changes from code,

the Assets Catalog allows us to set different resources for each appearance.
For instance, we can specify different images for Any, Light, or Dark. The
The interface of Apple devices can be presented with two appearances:
option is called Appearance (Figure 5-123, number 1).
Light and Dark. The user sets the preferred appearance from the Settings
app and the applications must adapt their interfaces to it. The appearance
Figure 5-123: Set of images for the Any and Dark appearances
by default is Light, but users can change it from the Display & Brightness
option, as illustrated below.

Figure 5-122: Different appearances for the interface

The images for the Dark appearance are added as before, by dragging the
file for each resolution to the squares on the screen. For instance, we can
complete this example to define a button for the Light appearance and
another for Dark.

Figure 5-124: Set of buttons for the Dark appearance


Once we have images for both appearances, we can test them from the
Storyboard or the simulator. The option in the Storyboard is available from
the Device Configuration panel (see Figure 5-21), and in the simulator the
option is available in the Features menu at the top of the screen (Toggle
Appearance). For instance, this is how the buttons look like on the
Storyboard when we change the appearance from Light to Dark. 

Figure 5-125: Button in Light and Dark appearances In this example, we have created a color set called MyColor with the color
white for the Light appearance and orange for the Dark appearance. This
color set can be later assigned to any view as we do with a normal color.
For instance, if we assign the MyColor set to the main view in the interface
of Figure 5-125, the background turns orange in Dark appearance.

Figure 5-127: Background with custom colors for Light and Dark
appearances

Most colors provided by the system are automatically adapted to the


appearance. This is the reason why the background changes from white to
black in our example (The color assigned to the main view by default is
called System Background Color and is white in Light appearance and black
in Dark appearance). But we can define our own sets of colors from the
Assets Catalog. The set is added from the Editor menu (Add New Set / 
Color Set) and the appearance is selected from the Attributes Inspector
panel. Xcode adds placeholders to the interface for each of the selected
options (Figure 5-126, number 1). Clicking on a placeholder adds options at
the bottom of the Attributes Inspector panel to select the color we want
for the appearance (Figure 5-126, number 2).

Figure 5-126: Colors for Light and Dark appearances

Icons Accent Color


 

Icons are the little images that the user taps or clicks to launch the app. By Along with the AppIcon set, the Assets Catalog also includes a set called
default, the Assets Catalog includes a set called AppIcon to manage the AccentColor to define the interface's accent color (also known as tint
icons we must provide for the application. The set includes placeholders color). This is the color used by some controls, such as buttons and sliders.
for every icon we need and for every scale and device available. The color by default is blue, but we can modify the AccentColor set to
Icons may be created with any image edition software available in the define a new one. For instance, below, we assign an orange color to the
market. A file must be created for every size required. For example, the Universal Accent Color. From that moment on, all the controls in every
first two placeholders require images of 20 points, which means that we device will be orange, unless we specify something else later.
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 Figure 5-129: Accent Color
created, we must drag them to corresponding squares, as we do with any
other image. Figure 5-128 shows what the AppIcon set may look like once
all the images are added (sample files are available on our website).

Figure 5-128: AppIcon set with the icons for iPhones and iPads


Launching Image

The launching image is the image that is shown when the app is launched.
No matter how small the app, it always takes a few seconds to load. These
images are required to give the user the impression that the app is
responsive. Xcode provides an additional file with a single scene to create a
launching image. This file is called LaunchScreen.storyboard, and it is
included in the Navigator Area along with the rest of the files created by
the template.
Apple’s guidelines recommend creating a launching image that gives the
user the impression that the app is already running. For example, we can
add an Image View with an image that simulates the app’s interface. Figure
5-130, below, shows a possible launching screen for the app created in our
last example. We added an Image View to the main view with the
background.jpg image set in the Aspect Fill mode. Because the background
is the same as the app’s background, the transition between this image
and the app’s interface will look smooth to the user. 

Figure 5-130: Launching Image for our app IMPORTANT: Although we could add any element from the Library
to this scene, only some of them are appropriate. The content of this
view is shown before the app is launched, so there is no code we can
process at that moment.

CHAPTER 6 - ADAPTIVITY 6.1 Adaptivity


Adaptivity is a term that describes the capacity of the interface to adapt to


changes in the size of the screen, either because the app is running on
devices with different screens, the space available is not the same as
before, or the device is rotated. Adaptivity is just an idea, accomplished by
the combination of two technologies called Auto Layout and Size Classes.
Combining these tools, we can develop different versions of the interface
for every device and orientation.
6.2 Auto Layout it all. Although we could solve this problem by assigning new values to the
view's frame property every time the space changes, this approach presents

a difficult challenge. All the values for every view in the interface must be
modified, not only when the screen is rotated but also for every type of
The position and size of the views added to the interface in the examples in
screen available. And the list of possible scenarios grows every time a new
Chapter 5 were determined by their current position and size on the scene.
device enters the market or new features are introduced, like the split-
If we wanted to move the view to a different position or change its size, we
screen mode in iPads that allow users to run two apps simultaneously. The
could have dragged the view or the little squares around it. When we
solution is provided by Auto Layout.
organize the interface this way, the final position and size on the scene
Auto Layout is a system that implements a set of rules to organize the
determines the position and size of the view. When the app is launched,
elements on the interface. The rules establish the relationship between
the objects that represent the views are created and the values that
elements and between the elements and their containers. These rules are
determine their positions and sizes on the scene are assigned to their frame
simple statements that determine things like how far a view should be
properties. Using the values in this property, the system draws the views
from another view, or how a group of views should be aligned. As an
on the screen. This is an intuitive way to organize the interface, but the
example, Figure 6-2, next, presents a container with a button, the position
initial values do not adapt to changes in the space available, orientation, or
and size of which are determined by four rules. These specific rules tell the
the device.
system how far the button should be from the sides of its container. The
example on the left is what we would see during implementation, when all
Figure 6-1: View positioned with absolute values
the rules are assigned to the element. The illustration on the right
represents the element during execution, when the position and size of the
button have already been modified to abide to the rules.

Figure 6-2: Rules applied to a button

This example shows what happens when we determine the position and
size of a view in portrait mode and then rotate the device. In landscape
mode, half of the gray box generated by the view disappears at the bottom 
of the screen because there is not enough vertical space available to show

The rules applied to the button in Figure 6-2 are saying things like "this Constraints
button always has to be 20 points from the top of its container" and "this

button always has to be 20 points from the left edge of its container".
Every rule determines one aspect of the relationship between the button
Computers cannot understand human expressions like "position the view
and its container to help the system determine the position and size of the
50 points from the edge of the container". For this reason, Auto Layout
button every time the space available changes. Because of this, we no
defines a set of specific rules that we can configure and combine to
longer need to declare explicit values to lay out the elements; they are
organize the interface. Auto Layout's rules are called Constraints, and they
calculated by the system after all the rules have been considered.
are comprehensive enough to allow us to create any design we want.
There are two main groups of constraints: Pin constraints and Align
IMPORTANT: When we use Auto Layout, the values assigned to the
constraints. Pin constraints define space, such as the space between a view
frame property of every view in the Storyboard are not considered by
and the edge of its container, while Align constraints define how the views
the system anymore. Assigning new values to this property has no
should be aligned with respect to other views or their containers. The
effect on the position and size of the element on the screen, but
following list describes the Pin constraints available.
there are situations in which the values of this property are still
useful. We will see some examples in further chapters.
Top Space—This constraint defines the space between the top of the
element and its nearest neighbor or the top edge of the container
(superview).
Bottom Space—This constraint defines the space between the
bottom of the element and its nearest neighbor or the bottom edge
of the container (superview).
Leading Space—This constraint defines the space between the left
side of the element and its nearest neighbor or the left edge of the
container (superview).
Trailing Space—This constraint defines the space between the right
side of the element and its nearest neighbor or the right edge of the
container (superview).
Width—This constraint defines the width of the element.
Height—This constraint defines the height of the element.
Aspect Ratio—This constraint defines the aspect ratio of the
element.
Equal Widths—This constraint declares that two elements will have side of the elements are called Leading and Trailing because they are
the same width. If the width of one of the elements changes, the applied to a side according to the human language set in the system. In
same value is assigned to the other element affected by the English, for example, the leading space is on the left side of the element
constraint. and the trailing space is on the right, but in Arabic languages is the
Equal Heights—This constraint declares that two elements will have opposite.
the same height. If the height of one of the elements changes, the
same value is assigned to the other element affected by the
constraint.

The following are the constraints for alignment:

Horizontal in Container—This constraint aligns the center of the


element with the center of its container in the horizontal axis.
Vertical in Container—This constraint aligns the center of the
element with the center of its container in the vertical axis.
Leading Edges—This constraint aligns two elements to their left
side.
Trailing Edges—This constraint aligns two elements to their right
side.
Top Edges—This constraint aligns two elements to their top edge.
Bottom Edges—This constraint aligns two elements to their bottom
edge.
Horizontal Centers—This constraint aligns two elements to their
horizontal center.
Vertical Centers—This constraint aligns two elements to their
vertical centers.
Baselines—This constraint aligns two elements to their baselines.

Most of these constraints are straightforward, but some of them adapt to


the circumstances. For example, the constraints that affect the left or right

Assigning Constraints

Every element on the interface requires constraints to define its position


and size. Xcode offers a series of buttons at the bottom of the Storyboard
to add and manage constraints.

Figure 6-3: Buttons to work with constraints

 

These buttons have different functions, from adding constraints to helping These menus list all the types of constraints described before, but they
us solve the issues that arise along the way. The button on the left (1) adapt the options to the types and number of elements selected. For
updates the values of the view’s frame if they do not match the values of example, the constraints that require two elements are disabled when only
the constraints, the next button (2) opens a menu with options to add one element is selected. After we activate the constraint that we want to
constraints for alignment (Align menu), the following button (3) opens a add to the element, the Add Constraints button is highlighted. Clicking this
menu to add constraints for space (Pin menu), the next one (4) opens a button, adds the constraint to the view. Figure 6-5 shows the process of
menu with options to help us solve issues with constraints or let the adding a Top Space constraint to a view.
system place the constraints for us, and the last button on the right (5)
embeds the view or the scene in containers like Stack Views or Navigation Figure 6-5: Adding a Top Space constraint to a single view
Controllers, as we will see later.
To add a constraint to an element, we select the element in the scene and
then click the button to open the Align or Pin menus. Figure 6-4 shows the
menus displayed by these buttons.

Figure 6-4: Align and Pin menus


The constraint's value is set by default to the value defined in the scene,
but we can change it to any value we want. In our example, we have
activated the Top Space constraint with a value of 20. This means that
when the system reads this constraint it will position the top of the view 20
points from its nearest neighbor. If there are no other elements at the top,
the distance is determined from the top of the element to the top edge of
its container. And if multiple destinations are available, like another view or
the container, we can click on the arrow to select which one we want to
use to connect the constraint.

 IMPORTANT: The Pin menu includes an option called Constraint to


margins. If the option is activated, the Space constraint is set
Space constraints are added from the Pin menu. We must select the view between the view and the margin of the main view. This margin is a
(Figure 6-5, number 1), click the Pin button at the bottom of the Editor standard value defined by Apple that varies from one device to
Area to open the Pin menu (Figure 6-3, number 3), activate the constraint another. Most views do not consider this option and therefore the
we want to add (Figure 6-5, number 2), and press the Add Constraint Space constraints are pinned to the edge of the main view, but if you
button to add it to the view (Figure 6-5, number 3). Space constraints are find that a view was pinned to the margin instead, you can remove
selected by clicking the red line between their value and the square in the the constraint and create it again with this option deactivated or
middle that represents the view, as shown below. select the constraint and remove the margin from the Attributes
Inspector panel.
Figure 6-6: Tool to add Space constraints
After the constraints have been selected and configured, we can press the
Add Constraints button at the bottom of the menu to effectively add the
constraints to the view. The menu is closed and Xcode shows lines in the
Storyboard representing the constraints. Every time an element is selected,
the lines that represent its constraints are shown on the screen (Figure 6-7,
number 1).

Figure 6-7: View with a Top Space constraint

specify yet how it should be positioned horizontally and how to determine


its size. There are several options available, depending on what we want to
achieve. For instance, if we want to position the view at the center of the
main view, we must add a Horizontal alignment constraint.

Figure 6-8: Adding an alignment constraint

Do It Yourself: Create a new project. Drag an empty view from the



Library to the main view (the option in the Library is called View).
Assign a background color, a size, and a position like the view
These types of constraints are added from the Align menu, as illustrated in
depicted in Figure 6-5. With the view selected, open the Pin menu, Figure 6-8. The constraints available for single elements are the Horizontal
and add a Top Space constraint (Figure 6-5). Keep the value of the in Container (to center the element horizontally) and the Vertical in
constraint as suggested by Xcode. You should see the line Container (to center the element vertically). The rest of the options are
representing the constraint as shown in Figure 6-7, number 1. only available when multiple elements are selected. All the options in this
menu also have a value that determines the constraint’s offset. By default,
Although a constraint determines a specific rule, one constraint alone the value is 0, which positions the view at the center of its container, but
cannot provide enough information for the system to determine the we can change it to displace the view to the left (negative values) or the
element's position and size. When this happens, Xcode shows the right (positive values). In this example, we have selected the Horizontal in
constraint in the Storyboard in red. The color indicates that the constraints Container constraint with an offset value of 0 to center the view
assigned to the element are insufficient. There is a total of three colors: horizontally in its container (the main view).
blue, orange and red. The constraints are shown in blue when the system Now, the view is centered on the screen, but the constraints are still red.
has enough information to display the element, orange when the element The two constraints we have added so far (the Top Space constraint and
is not in the position determined by the constraints, and red when there the Horizontal in Container constraint) tell the system that the top of the
are not enough constraints, or some are in conflict (the system cannot view must be 20 points from the top of the container and aligned at the
satisfy all the constraints at the same time). center. This is enough to establish the position, but we haven’t provided
In our example, the constraint is red because the system knows about the the information for the size. The box could expand or contract to always be
vertical position of the view (the value of the y coordinate), but we did not at a certain distance from the sides of the container, as we did with the
button in Figure 6-2, or it could have a specific size. For this example, we
have decided to go with the last option and give our view a size of 125 by All the constraints are now blue because the system has enough
125 points with Width and Height constraints. information to draw the view. The Top Space and Horizontal in Container
constraints set the position, while the Width and Height constraints
Figure 6-9: Adding the Width and Height constraints determine the size. No matter what the orientation or the size of the
screen, our view will be drawn at 20 points from the top, at the center of
the screen, and with a size of 125 by 125 points.

Do It Yourself: Add the Horizontal in Container constraint to the


view, as shown in Figure 6-8, and the Width and Height constraints,
as shown in Figure 6-9. Click on the Orientation button to change the
orientation of the scene to landscape (Figure 5-21, number 2). You
should see the view at the center and 20 points from the top, no

matter the orientation.
The Width and Height constraints tell the system that the view will always
have a specific width and height. The size is determined by the values Unless we have a good reason to set a specific size, we should always let
declared for each constraint in the Pin menu, as shown in Figure 6-9, the size of the elements be determined by the relationship between the
number 1. By default, the menu shows the current values defined in the element and its siblings or the container. For instance, if we want the view
Storyboard, but we can change them to declare the size we want. Figure 6- of our example to always be at the center of the screen, it is better to set
10, below, shows our view with all of its constraints. Leading and Trailing constraints to define the space on the sides and let the
system determine the width, as illustrated below.
Figure 6-10: View with all the necessary constraints
Figure 6-11: Space constraints to define the width of the element

In this example, the position and size of the view are determined by the 
Top Space (1), Height (2), Leading Space (3), and Trailing Space (4)
constraints. The Leading Space and Trailing Space constraints tell the The example in Figure 6-12 implements an Aspect Ratio constraint to
system that the view must be at a certain distance from the edge of the determine the view’s height. The width is determined by the Leading Space
container, and it should shrink or expand to always be at that distance. and Trailing Space constraints, but the height is defined to be proportional
The view in Figure 6-11 has an absolute height determined by the Height to the current width by the Aspect Ratio constraint. When the view
constraint, but we could also change that assigning a Bottom space expands because of the Space constraints on the sides, it also gets
constraint or an Aspect Ratio constraint, as in the following example. proportionally taller. Because of these constraints, the view always has the
same rectangular shape.
Figure 6-12: Aspect Ratio constraint
Figure 6-13: Same aspect ratio in portrait and landscape


Editing Constraints
Do It Yourself: To reproduce the interface in Figure 6-13, you can

select and erase the Height constraint from the previous example
and add an Aspect Ratio constraint instead (see Figure 6-12). Click on Every constraint has associated values to determine its properties, such as
the Orientation button to change the orientation of the scene to the distance, the width of the element, etc. Assigning new values to the
landscape. You should see something like Figure 6-13 (right). constraints changes how they affect the elements and how the interface is
built. The Align and Pin menus only allow us to add new constraints, not to
IMPORTANT: The aspect ratio is a value that determines how many edit the ones already created. Xcode offers different alternatives to access
points on one side correspond to the points on the other side. For and edit the constraints. The simplest is to select the element and open
example, a ratio of 1:2 determines that for each point on one side, the Size Inspector panel in the Utilities Area (Figure 5-22, right). The
the other side should have two. The Aspect Ratio constraint sets this constraints associated with the element are listed at the bottom.
value according to the current aspect ratio of the view. Next, we will
see how to modify this value and specify a custom ratio. Figure 6-14: List of constraints on the Size Inspector panel

The constraints are listed at the bottom of the panel. Figure 6-14 shows the
four constraints added to the view of our last example (Trailing Space,

Leading Space, Top Space, and Aspect Ratio). Each constraint on the list (Figure 6-14), select the constraint from the Storyboard, or click on the
includes an Edit button to edit the values. Figure 6-15, below, shows the item that represents the constraint inside the Document Outline panel.
window that opens when we click on this button. Changing the values in When a constraint is selected, its values are shown in the Utilities Area.
this window automatically modifies the constraint (this menu also opens if Figure 6-16, below, shows this panel after the Top Space constraint from
we double-click the constraint in the Storyboard). our example is selected. From this panel, we can change all the constraint’s
values and some aspects of the elements the constraint is connected to.
Figure 6-15: Popup window to edit the values of a constraint
Figure 6-16: Constraint editor in the Utilities Area

There are different options available to configure a constraint. The option 


that determines the value of the constraint, such as the distance or the
width, is called Constant.

Do It Yourself: Select the scene in the Storyboard. Click on the


button to show the Size Inspector panel (circled in Figure 6-14). Click
on the Edit button of any of the constraints to change their values.
Try, for example, to assign a value of 50 to the Top Space constraint,
as we did in Figure 6-15. The changes will be immediately reflected
in the Storyboard when possible.

Constraints have their own panel with more configuration options. To open
this panel, we can double click the constraint on the Size Inspector panel
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 inside the scene’s view after all the toolbars and special views
are displayed by the system. For example, if we don’t specify otherwise,
the system displays a status bar at the top of the screen. This bar can adapt
to different devices and orientations, and it might be completely removed
at any time. To make sure that our content never overlaps the bar, we can
pin it to the edges of the Safe Area. 

Figure 6-17: Safe Area By default, the pin constraints assigned to the view are attached to the Safe
Area, so the view will be drawn below the status bar. If instead we want
our view to be pinned to the top edge of the main view, we must change
the constraint’s configuration from the Size Inspector panel.
When we select a constraint, the Size Inspector panel shows the two
elements the constraint is attached to. To connect the top of our view to
the top of the main view, we must change the second item of the
relationship to Superview, as shown below.

Figure 6-19: Pinning a view to its superview

For example, the following interface includes a view that is positioned and
sized with pin constraints with a value of 0. This expands the view to the
edges of the scene.

Figure 6-18: Pinning a view to the Safe Area 

Figure 6-20, below, shows what we see in the simulator with the Top The menu displays a list of all the elements available to attach to the
constraint attached to the Safe Area (left), and what happens when we constraint. In this example, only the Safe Area and the main view are
attach it to the superview (right). available, but there may be others, as we will see later.

Figure 6-20: View pinned to the Safe Area or the superview

The option to select the element attached to the constraint is also available
in the Pin menu. When we select a Space constraint to add it to a view, we
can click the arrow on the right-hand side of the value and select the
destination view, as shown below.

Figure 6-21: Selecting the destination view


Resolving Auto Layout Issues
The interface in Figure 6-22 presents a simple view with a Top Space

constraint and Width and Height constraints to determine its size. The
horizontal position was specified by a Leading Space constraint, but the
As mentioned before, Xcode shows the constraints in different colors
view was moved to the right and therefore Xcode shows dashed lines
depending on the information they provide. We have seen the lines in red
around the area in which it should be according to the constraints. This is
when there were not enough constraints to position or size the element
just an indication, and we could keep adding or modifying the constraints
and blue when the system had all the information required to place the
until the design is over, but if we need the Storyboard to represent the final
element on the screen. But there is another situation. Sometimes the
interface, we must solve the issue. One option is to update the view’s
constraints are enough, but Xcode cannot determine the position and size
frame to match the constraints’ values. This is done by selecting the view
of the views in the Storyboard and therefore there is a mismatch between
and clicking on the Update Frames button at the bottom of the Editor Area
what we see in the Storyboard and what the user will see after the
(Figure 6-3, number 1). Once we press this button, the values of the view’s
constraints are applied. This situation may occur because Xcode decides
frame are updated, and the view is moved to the place determined by the
that it is better to show the view in the current position and with the
constraints.
current size to make easy for us to edit the interface, or it just cannot
determine the new position and size under the present conditions. To help
Figure 6-23: Element in the position determined by the constraints
us identify the problem, Xcode shows the constraints in orange with
dashed lines that delimit the area where the view should be.

Figure 6-22: Element in the wrong position

For complex issues, Xcode offers the Resolve Auto Layout Issues menu
(Figure 6-3, number 4). The Resolve Auto Layout Issues menu not only
allows us to solve problems with the constraints, but it can also help us
determine what the right constraints are for our interface. The following

are the options available (the options are available twice, one for the
selected views and another for all of them).

Update Constraints Constants—This option updates the values of


the constraints to match the current position and size of the view.
Add Missing Constraints—This option adds the constraints for us.
Xcode estimates what the constraints should be according to the
current position and size of the views in the Storyboard and
automatically adds those constraints. It is useful as a starting point.
Reset to Suggested Constraints—This option modifies the current

constraints to take them back to the state established by Xcode when
the suggested constraints were generated.
This button displays a list of the errors and warnings that are affecting our
Clear Constraints—This option erases the constraints. We can select application. Figure 6-25, below, shows the warning displayed when a view
it when the constraints are not working and we have decided to start is not in the position determined by the constraints.
from scratch, but it is also useful when working with different Size
Classes, as we will see later. Figure 6-25: Current issues with constraints

Xcode offers tools with more specific information to help us identify and
solve complex problems with constraints. An easy way to get this
information is from the Error button at the top of the Editor Area (Figure 6-
24, number 1).

Figure 6-24: Errors and Warnings 

Another way to get a detailed explanation of the issues regarding our


constraints is from the Document Outline panel (Figure 5-21, number 1).
The panel displays a list of the elements and constraints in the interface,
and it provides a small button at the top right corner whenever there is an
issue with the constraints (Figure 6-26, number 1).

Figure 6-26: Getting the list of issues from the Document Outline panel
includes another button (Figure 6-27, number 1) to open a popup menu
with suggestions and solutions.

Figure 6-28: Automatic solutions for constraint issues

 Some of the options in this menu are available in the Resolve Auto Layout
Issues menu, but they are adapted to the issue we are dealing with.
When we click this button, the list of views and constraints is replaced by
the list of issues our interface currently has.

Figure 6-27: List of issues in the Document Outline panel

All the issues are listed with detailed information, such as the current and
expected values, the constraints needed, etc. Every item of the list also

Intrinsic Content Size Listing 6-1: Reading the intrinsic content size


import UIKit

Elements like labels and buttons generate their own content. This content class ViewController: UIViewController {
@IBOutlet weak var mytitle: UILabel!
defines a minimum size that the element's view adopts when no size is
determined by the constraints. For example, the intrinsic content size of a override func viewDidLoad() {
label is determined by the size of its text. Because of this property, super.viewDidLoad()
mytitle.text = "Description: My New Label"
sometimes all we need for an element is to declare enough constraints to
determine its position. let labelSize = mytitle.intrinsicContentSize
print("New Width: \(labelSize.width)")
print("New Height: \(labelSize.height)")
Figure 6-29: Label defined with intrinsic content size }
}

Do It Yourself: Delete the elements in the scene. Add a label with a


Top Space and a Leading Space constraint to define its position (see
Figure 6-29). Create an Outlet for the label called mytitle. Update the

ViewController class with the code in Listing 6-1 and run the
application. The label's text is modified as soon as the app is
The label in Figure 6-29 has only two constraints, Top Space of 30 points
and Leading Space of 16 points. There are no requirements for its size. If no executed, and the new values of its intrinsic content size are printed
constraints are defined, the label’s view automatically adopts the size of its on the console.
text.
If we need to know what the current intrinsic content size of a view is, the
UIView class includes the following property to return this value.

intrinsicContentSize—This property returns a CGSize value with the


view’s intrinsic content size.

The following example connects a label with an Outlet called mytitle, assigns
a new text to the label, and prints the new values of its intrinsic content
size on the console.
Multiple Views Constraints

Xcode provides an additional tool to add constraints that makes it easy to


connect elements with each other. It requires the same procedure we use
to create Outlets and Actions, but instead of control-dragging a line to the
view controller we do it to another view, as shown below.

Figure 6-30: Control-drag to add a constraint

The menu shows multiple options depending on what Xcode thinks we are
trying to do. For example, when we control-drag a horizontal line from one
view to another, the menu shows the Horizontal Spacing option at the top
to let us add a Space constraint between the views.
 Although this technique is useful to connect views together, we can also
use it to add all the same constraints applied before. For example, by
When the mouse button is released, Xcode shows a popup menu over control-dragging a line from a view to its superview we can add a Top
the target to select the constraint we want to add. Space constraint.

Figure 6-31: Quick menu to add constraints Figure 6-32: Adding a Top Space constraint to the superview

 

IMPORTANT: If you hold down the Command key or the Shift key When working with two views connected with each other, we need to
when the menu is open, you can select multiple constraints. After all make sure that the constraints provide enough information for the system
the constraints are selected, press the Return key to add them to the to position and size both views, but there are different options available,
views. depending on what we want to achieve. For instance, we can define the
width for only one view and let the system determine the width of the
When we drag and drop a line inside the same view, the menu displayed by other. In the example of Figure 6-34, below, we pinned the views to the
Xcode includes options like Width, Height, and Aspect Ratio. Figure 6-33 sides and with each other, but only added a Width constraint for the view
shows the options available after a horizontal line is dragged from and into on the left.
the view on the left.
Figure 6-34: Adapting multiple views to the space available
Figure 6-33: Popup menu to add a Width constraint

Because we defined a fixed width for the left view only, that view is always
of the same size while the view on the right expands or contracts to fill the
space available. If we had defined both views with a fixed size, the system
would not have been able to adapt the interface to the size of the screen
and it would have returned an error message.

Do It Yourself: Delete the elements in the scene. Add two square


views as in Figure 6-30. Control-drag from the left view to the right
view to add a Space constraint (the option is called Horizontal
Spacing). Control-drag from the left view to the top of the main view 
to add a Top Space constraint (the option is called Top Space to Safe
Area). Repeat the process for the right view. Control-drag from the The supplementary view is centered in the main view, and it has a fixed
left view to the left side of the main view to create a Leading Space width and height, and the labels are pinned to the sides of this view and
constraint (called Leading Space to Safe Area). Repeat the process centered with Vertical Center constraints. The result is that the labels are
always oriented towards the center of the main view.
with the view on the right to create a Trailing Space constraint
To hide the view, we can select the option Hidden from the Attributes
(called Trailing Space to Safe Area). Control-drag a horizontal line
Inspector panel or assign the value true to the view’s isHidden property from
inside the view on the left to create a Width constraint (Figure 6-29).
code. This property makes the view visible during development, but it
Finally, control-drag a vertical line inside each view to create Height hides it when the app is executed.
constraints.
Do It Yourself: Delete the elements in the scene. Add a square
Another problem that arises when working with multiple views is how to view at the center of the main view with a Top Space constraint, a
align them to a single point. A common solution involves the use of Horizontal in Container constraint, and Width and Height constraints,
invisible views in between. The following example centers two labels in the
as shown in Figure 6-35. Add two labels on each side of the view.
main view using a third view as a separator.
Control-drag lines from each label to the view to add a Space
Figure 6-35: Labels centered with an invisible view constraint (Horizontal Spacing) and a Vertical Center constraint
(Center Vertically). Select the view and mark the option Hidden from
the Attributes Inspector panel. Change the orientation of the scene
to see how the labels adapt to the space available.

Besides the Vertical Center constraint applied in the last example to center
the labels vertically with the view, there are other alignment constraints
available. These constraints are added from the Align menu and their
application is straightforward, as shown below.

except for the label at the top. The width of this label is determined by a
Figure 6-36: Alignment constraints for multiple views Trailing Edge constraint connected to the button (called Trailing on the
quick menu). Because of this, the label at the top always expands to reach
the right side of the button (Figure 6-37, number 1).

These types of constraints provide the alternative to build more complex


layouts. For example, they can help us determine the size of an element
according to the space occupied by two or more elements. Figure 6-37,
below, illustrates this situation.

Figure 6-37: Label’s width determined by a Trailing Edge constraint

This example includes two labels, "Description" and "Please, press the",
and a Plain button with the title "Button". All the elements have Space
constraints to the sides of the container and between them to establish
their positions. Their sizes are determined by their intrinsic content size,
Relations and Priorities and a view. The label was defined with a Width constraint of 100, a Leading
Space constraint that pins it to the left side of the main view, and a Vertical

Center constraint that keeps it vertically centered with the view. The view
has a Height constraint to define its height and Space constraints to define
The constraints we have defined so far are constant. This means that their
its position and the distance between its left side and the label, but its
values do not change, and the system must satisfy every one of them or
width is determined by the space available. The result is that the view
return an error in case of failure. But some interfaces require more
expands or contracts, but the label has always the same width (100 points).
flexibility. Sometimes constraints need only an approximate value or they
are not required all the time. For these situations, constraints include two
Figure 6-38: Label with a fixed width of 100 points
more configuration values called Relation and Priority. The Relation
determines the relationship between the constraint and its constant value,
while the priority determines which constraint is more important.
Important constraints are satisfied first and then the system tries to satisfy
the rest as close as possible. The possible relations are the following:

Equal determines that the constraint’s value must be equal to the


constant value. This is the relation by default.
Less Than or Equal determines that the constraint’s value can

be equal or less than the constant value.
Greater Than or Equal determines that the constraint’s value
A problem arises when a text longer than 100 points is assigned to the
can be equal or greater than the constant value.
label. Because the Width constraint determines a fixed size of 100 points,
the new text is truncated, as shown below.
On the other hand, the priorities are defined by an integer value between
1 and 1000. For common situations, Xcode declares three predefined
Figure 6-39: The longer text is clipped
states:

Low (250) determines a low priority.


High (750) determines a high priority.
Required (1000) determines that the constraint is required.

There are multiple situations in which these values become useful. For
example, we may have a label that we want to be of a specific size, but to
change if new text is assigned to it. The following interface includes a label

 

What we want is for the label to stay at a size of 100 points but to grow Do It Yourself: Delete the elements in the scene. Add a label on
when the space is not enough to show the full text. We need the value of the left side of the main view with the text "My Title" and a size of
the Width constraint to be Greater Than or Equal to 100 points. To change 24 points. Insert a view on the right with a gray background (Figure
the relation, we must select the constraint, click the button on the right 6-38). Add a Leading Space constraint and a Trailing Space constraint
side of the Relation value, and select the option from the quick menu, as to the label. Add a Width constraint to the label with a value of 100.
shown next. Add a Top Space constraint and a Trailing Space constraint to the
view. Add a Height constraint to the view to define its height. And
Figure 6-40: New Relation for the constraint
lastly, add a Vertical Center constraint between the label and the
view (Figure 6-38). Assign the text "Description Label" to the label
from the Attributes Inspector panel (Figure 6-39). Select the label's
Width constraint and change its relationship to Greater Than or
Equal, as shown in Figure 6-40. You should see something like Figure
 6-41.

After the Greater Than or Equal relation is selected, the system tries to There are times when two or more constraints must be constant, but they
keep the label's width at 100 points whenever possible, but it lets the label cannot be always satisfied. In cases like this, the system needs to know
expand if necessary. Now the text is not truncated when it is longer than which one is more important. In the interface of Figure 6-42, below, two
100 points and the view fills the remaining space. views are on the sides of a label. The views expand to occupy the space
around the label, but they cannot reduce their sizes further than 50 points.
Figure 6-41: The label expands to display the full text
Figure 6-42: Testing conflicting constraints

In our example, the label was defined with a Width constraint equal to 250
points. The views are pinned to the main view and to the label with Space 
constraints and they also have a Width constraint but this time Greater
Than or Equal to 50. The views are also connected to each other by an IMPORTANT: In these situations, the app will not crash but the
Equal Width constraint to make their widths equal. This means that the system will print an error on the console with the message "Unable
label will always have the same size, but the views will expand or contract to simultaneously satisfy constraints" followed by a list of all the
to fill the space available. When there is plenty of space, the constraints constraints that couldn’t be satisfied.
work as expected. The views expand to fill the space between the label and
the edge of the main view (Figure 6-42). A problem arises when there is By default, the Width constraint of the label is equally important than the
not enough space to simultaneously satisfy all the Width constraints. The Width constraints of the views and the system does not know what to do
label must always be 250 points wide, and the views’ width cannot be less when there is not enough space to satisfy them all. The solution is simple.
than 50 points. The constraints are fine when there is enough space, but We must tell the system that it is important for us that the width of the
when there is no room for all the elements, they are displayed in red, as views is never less than 50 points, but we do not care as much about the
shown below. width of the label. This is achieved by reducing the priority of the label’s
width.
Figure 6-43: Unable to satisfy constraints The constraints with a higher-priority value are satisfied first, and then the
system tries to satisfy the rest as close as possible. Figure 6-44, below,
shows what we see if we assign a priority of, for example, 750 to the label’s
Width constraint. Now the label's width is reduced to make room for the
views and the constraint that is not satisfied is shown with a dashed line.

Figure 6-44: Label’s Width constraint with a lower priority



The Content Hugging priority determines how much the element wants to
IMPORTANT: Besides Constant, Relation, and Priority, there is an stay of the size of its content and avoid expanding (it hugs its content). On
additional value available to configure constraints called Multiplier. the other hand, the Content Compression Resistance priority determines
The multiplier is multiplied by the value of the constant to get a how much the element wants to stay of the size of its content and avoid
proportional value. For example, when the Multiplier value of an contracting (resist compression). Figure 6-46, below, presents an example
Equal Width constraint is set to 2, the element will be twice as wide of two labels with a visible background to demonstrate how this works.
as the original. The system also uses the Multiplier to set the value
for the Aspect Ratio constraint. Figure 6-46: Interface to test content priorities

There are two more types of priorities that are useful for elements with
intrinsic content size, such as labels, buttons, and images. They are called
Content Hugging priority and Content Compression Resistance priority.
Figure 6-45 shows the controls available on the Size Inspector panel to
modify their values.

Figure 6-45: Options to modify content priorities 

The labels are pinned to the main view and to each other with Space
constraints. Their widths were not defined, so they will expand or contract
according to the space available. Which label expands, and which one
contracts is decided by the system considering their priorities. For this
example, we have decided to assign to the label on the right higher values
for its priorities. Its horizontal Content Hugging priority was declared as IMPORTANT: The priorities can be set for both the horizontal and
750 and its Content Compression Resistance priority was declared as 1000. vertical axis. The horizontal values usually apply to elements like
This will not let the label on the right expand or contract, so it is always the labels and buttons, while the vertical values are frequently used with
label on the left that adapts to the space available. Text Fields and images.

Figure 6-47: Labels with different content priorities

If, for example, we want the label on the right to contract when there is not
enough space, we can change its Content Compression Resistance priority
to a value smaller than the value of the label on the left. Figure 6-48 shows
the result of assigning a value of 250 to the Content Compression
Resistance priority to the label on the right and a value of 750 to the label
on the left. The label on the left continues to expand when more space is
available, but now it is the label on the right that contracts to make room
when there is not enough space for both.

Figure 6-48: Labels with different compression values

Stack Views size of the container. The trick is to assign Space constraints to the sides
and at the top and bottom of the content. The system calculates the width

and height of the content from these constraints and expands or contracts
the container to match the size of its content. In the following example, we
Constraints were developed to affect single properties, such as a specific
assigned Leading, Trailing, Top and Bottom space constraints to the label.
dimension of an element or the distance from one element to another.
From these constraints and the label's intrinsic size, the system can
Because of this, designing an interface, even a simple one, requires an
calculate the size of the container view and therefore we no longer need
intricate network of constraints interconnecting the elements together.
the Width and Height constraint for the view (the size of the content
Fortunately, this elaborated system can be simplified by including our
determines the size of its container).
views inside other views that act as containers, which distributes the
constraints in hierarchy levels.
Figure 6-50: The size of the container is defined by the content
In the following example, we have positioned a view at the top of the main
view and added a label inside. The constraints for the view are connected
to the main view (left), but the constraints for the label are connected to
its container (right).

Figure 6-49: Views inside views


Using views as containers is a common practice. They create a hierarchy of


constraints that simplify our work, but they still present a challenge. Every
time we need to modify the interface, adding or removing elements from
these containers, we still have to deal with their constraints. Considering

that containers usually display their content in columns or rows, UIKit
defines the UIStackView class to simplify this work. This class creates a
Figure 6-49 shows the constraints for the view and the label. The view has
special view that manages a horizontal or vertical stack of views and all
a Width and a Height constraint to define its size and a Top and a
their constraints. This means that using a Stack View to contain our views,
Horizontally alignment constraint to determine its position. The label, on
we do not have to worry about their constraints anymore; all we need to
the other hand, has Horizontal and Vertical alignment constraints to
do is to add or remove the views we want to include in the stack and the
determine its position inside the view. Because of this distribution, the
Stack View takes care of assigning all the necessary constraints to place
view may move on the screen according to the space available, but the
them horizontally or vertically. The following is one of the initializers
label is always at the center of its container.
provided by the class to create these container views.
The width and the height of the container view were determined by a
Width and Height constraints, but we can also let the content define the
UIStackView(arrangedSubviews: [UIView])—This initializer size), equalSpacing (the space between the views is distributed equally),
creates a Stack View that contains the views specified by the and equalCentering (the space between the center of the views is equal).
argument. The arrangedSubviews argument is an array with spacing—This property sets or returns a CGFloat value that defines
references to all the views we want to include in the stack. the space between views.
addArrangedSubview(UIView)—This method adds a view to the
As always, the Library offers the options to add them to our interface.
end of the Stack View.
Figure 6-51: Horizontal and Vertical Stack Views in the Library insertArrangedSubview(UIView, at: Int)—This method adds a
view to the Stack View at the index specified by the at argument.
removeArrangedSubview(UIView)—This method removes a view

from the Stack View.
The class provides the following properties and methods to organize the
To include a Stack View in our interface, we just need to drag the option
views and the space available inside the Stack View.
from the Library to the scene. The Stack View is represented by a white
rectangle that we can pin to the main view, the Safe Area, or other views.
arrangedSubviews—This property returns an array with references
Once the Stack View is in place, we can add all the subviews we want.
to the views managed by the Stack View.
axis—This property sets or returns a value that defines the Stack Figure 6-52: Buttons inside a Stack View
View’s axis (the orientation in which the views are going to be laid
out). It is an enumeration called Axis included in the UILayoutConstraint
class. The values available are horizontal and vertical.
alignment—This property sets or returns a value that determines
how the views inside the Stack View are going to be aligned. It is an

enumeration called Alignment included in the UIStackView class. The
values available are fill, leading, center, and trailing.
In this example, we pinned the Stack View with Space constraints to the
distribution—This property sets or returns a value that determines top and the sides of the Safe Area and then dragged three buttons inside.
how the views are going to be distributed along the Stack View’s axis. Notice that we did not define the view’s height because the system can
It is an enumeration called Distribution included in the UIStackView class. determine the size from the size of the content. Every time we add or
The values available are fill (the views are resized according to their remove a view from the stack, the Stack View’s height is updated (see
index), fillEqually (the views are equally resized), fillProportionally (the Figure 6-52).
views are resized proportionally according to their intrinsic content Once the content of the Stack View is defined, we can determine how the
views are going to be aligned. By default, the alignment property is set to fill,

which means that the views will expand to occupy all the space available,
but we can also align them to the center, left or right by changing the Stack
View’s configuration, as shown below (we assigned a gray background to
the buttons to be able to see the space occupied by their views).

Figure 6-53: Views inside a Stack View with different alignments

The value of the property may be defined from code through an Outlet
connected to the Stack View, or from the Attributes Inspector panel. To see
the values, we must select the Stack View from the Document Outline

panel or by holding down the Shift key and clicking the mouse’s right
button over the Stack View’s content. The options include the values for
In this example, we assigned a value of 15 to the spacing property. We can
alignment, distribution, and spacing. The latter generates a space between
also change the distribution to define how the space between views is
the views.
distributed. In the following example, we pinned the Stack View to the
bottom of the Safe Area and apply some of the values of the distribution
Figure 6-54: Space between views
property to it (Fill, Fill Equally, and Equal Spacing, in that order).

Figure 6-55: Different distributions


Stack Views can only manage one row or one column of views
(horizontal or vertical), but they can be nested, providing an alternative for
the creation of more complex arrangements. For example, we can add a
horizontal Stack View at the top of our vertical Stack View to show two
images side by side.

Figure 6-56: Nested Stack Views

 The interface in Figure 6-57 contains an empty vertical Stack View with
Top, Leading and Trailing constraints to define its position and width, and a
In Figure 6-56, we added a horizontal Stack View on top of the buttons Height constraint to define its height. Now we can incorporate any view we
with a fillEqually distribution and a spacing of 10, and then put two Image want to the interface by adding it as the content of this Stack View.
Views inside (Figure 6-56, center). The horizontal Stack View positions the
Image Views side by side, with a width determined by the space available Listing 6-2: Adding a view to the Stack View programmatically
and a height determined by the height of the images. Therefore, to give 
the horizontal Stack View a specific height, we had to assign a Height import UIKit
constraint (Figure 6-56, number 1).
class ViewController: UIViewController {
The advantage of using Stack Views to organize the interface is clear; we @IBOutlet weak var myStack: UIStackView!
define the constraints to place the Stack View and the view takes care of
override func viewDidLoad() {
assigning the constraints to its content. Depending on our design, we may super.viewDidLoad()
use Stack Views to organize most of our interface or work with constraints, let myButton = UIButton(configuration: .filled(), primaryAction: UIAction(title: "Press Here",
handler: { action in
as we did so far. But Stack Views are also useful when we need to add to print("Hello")
the interface a view from code, as in the following example. }))
myStack.addArrangedSubview(myButton)
}
Figure 6-57: Empty Stack View }

This view controller defines an Outlet called myStack to access the Stack Constraint Objects
View and then adds a UIButton to the stack with the addArrangedSubview()

method. Because the button is the only view contained by the Stack View,
it takes its position and size.
The information for the constraints is stored as XML code in the
Storyboard’s file along with the rest of the information for the interface.
Figure 6-58: Button added to a Stack View from code
Once the application is executed, the objects that represent the constraints
are created. These objects are instances of the NSLayoutConstraint class. The
objects defined in the Storyboard are automatically created from this class,
but we can also use it to create our own constraints from code. The class
includes the following initializer.

NSLayoutConstraint(item: Any, attribute: Attribute,


relatedBy: Relation, toItem: Any?, attribute: Attribute,
multiplier: CGFloat, constant: CGFloat)—This initializer creates a
 object from the information provided by the
NSLayoutConstraint
arguments. The item and toItem arguments are references to the
elements affected by the constraint, the attribute argument specifies
the type of constraint on each side of the connection, and the
relatedBy, multiplier, and constant arguments provide the values.

This initializer takes all the necessary values to configure the constraint,
including the views involved, the value of the Relation, Multiplier and
Constant, and the attributes of the views that are going to be controlled by
the constraint, such as the side, dimension, etc. The NSLayoutConstraint class
includes the following enumerations to set these attributes and their
relation.

Attribute—This enumeration defines the part of the object that


should be used to set the constraint. The possible values are left, right,
top, bottom, leading, trailing, width, height, centerX, centerY, baseline, and
notAnAttribute.
Relation—This enumeration defines the relation between the first let mylabel = UILabel(frame: CGRect.zero)
mylabel.text = "Hello World"
and the second attributes. The possible values are lessThanOrEqual, mylabel.textAlignment = .center
equal, and greaterThanOrEqual. mylabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mylabel)

The NSLayoutConstraint() initializer returns NSLayoutConstraint objects but do let constraint1 = NSLayoutConstraint(item: mylabel, attribute: .top, relatedBy: .equal, toItem:
not add the constraints to the view. The constraints are managed by view, attribute: .top, multiplier: 1, constant: 50)
methods of the UIVIew class. let constraint2 = NSLayoutConstraint(item: mylabel, attribute: .width, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200)
let constraint3 = NSLayoutConstraint(item: mylabel, attribute: .centerX, relatedBy: .equal,
constraints—This property returns an array with the toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
NSLayoutConstraint objects that represent the constraints managed by view.addConstraints([constraint1, constraint2, constraint3])
}
the view. }

addConstraint(NSLayoutConstraint)—This method adds a
constraint to the view.
This example adds the label to the main view and then defines its
addConstraints([NSLayoutConstraint])—This method adds an constraints. The Top Space constraint is added by establishing a relation
array of constraints to the view. between the label’s top attribute and the main view’s top attribute. The
removeConstraint(NSLayoutConstraint)—This method removes Width constraint, on the other hand, only affects one element. In this case,
from the view the constraint referenced by the argument. the values for the second element and its attribute are not necessary, so
removeConstraints([NSLayoutConstraint])—This method they are declared as null with the values nil and notAnAttribute, respectively.
removes from the view the constraints referenced by the argument.
IMPORTANT: When elements are introduced to the interface
The NSLayoutConstraint initializer takes one or two views, depending on the without constraints, Xcode uses an old system called Autoresizing to
type of constraint, the attributes we want to associate to the constraint, position and size them. The information generated by this system is
and its values. The following example applies a Top Space, a Width, and a later applied to create constraints for the elements and present the
Horizontal in Container constraints to a label. interface on the screen. If the constraints are added from code
though, the system doesn't know that there are constraints available
Listing 6-3: Laying out elements with constraints defined in code and tries to create its own. To tell the system that the Autoresizing
 constraints are not necessary, you must set the view's
import UIKit translatesAutoresizingMaskIntoConstraints property to false. Assigning the
value false to this property avoids the creation of automatic
class ViewController: UIViewController {
override func viewDidLoad() {
constraints and ensures that those you define in code will not
super.viewDidLoad() conflict with those defined by the system. The property must be
defined for every view added to the interface.

let constraintButton2 = NSLayoutConstraint(item: button, attribute: .centerX, relatedBy: .equal,


toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
Constraints created from code may be added or removed. The following view.addConstraints([constraintButton1, constraintButton2])
example takes advantage of the removeConstraint() method of the UIVIew class }
func moveLabel() {
to change the position of a label when a button is pressed. let text = mylabel.text!
switch text {
Listing 6-4: Removing constraints case "Right":
view.removeConstraint(constraintRight)
 view.addConstraint(constraintLeft)
import UIKit mylabel.text = "Left"
case "Left":
class ViewController: UIViewController { view.removeConstraint(constraintLeft)
var mylabel: UILabel! view.addConstraint(constraintCenter)
var constraintLeft: NSLayoutConstraint! mylabel.text = "Center"
var constraintRight: NSLayoutConstraint! case "Center":
var constraintCenter: NSLayoutConstraint! view.removeConstraint(constraintCenter)
view.addConstraint(constraintRight)
override func viewDidLoad() { mylabel.text = "Right"
super.viewDidLoad() default:
mylabel = UILabel(frame: CGRect.zero) break
mylabel.text = "Center" }
mylabel.translatesAutoresizingMaskIntoConstraints = false }
view.addSubview(mylabel) }

let constraintLabel = NSLayoutConstraint(item: mylabel!, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1, constant: 50)
The code in Listing 6-4 creates a label and a button. The button must
view.addConstraint(constraintLabel)
always be at the center of the screen, so we added a permanent Horizontal
constraintLeft = NSLayoutConstraint(item: mylabel!, attribute: .left, relatedBy: .equal, in Container constraint setting the centerX attributes of the button and the
toItem: view, attribute: .left, multiplier: 1, constant: 20)
constraintRight = NSLayoutConstraint(item: mylabel!, attribute: .right, relatedBy: .equal, main view as equal, but the label moves every time the button is pressed. To
toItem: view, attribute: .right, multiplier: 1, constant: -20) achieve this, we have defined three properties with the necessary
constraintCenter = NSLayoutConstraint(item: mylabel!, attribute: .centerX, relatedBy:
.equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
constraints: constraintLeft, constraintRight, and constraintCenter. After the
view.addConstraint(constraintCenter) constraints for the button are defined, these three constraints are also
defined and the constraintCenter constraint is added to the label to establish
let button = UIButton(configuration: .filled(), primaryAction: UIAction(title: "Move Label",
handler: { [unowned self] action in its initial position at the center. Because of this, the initial interface
self.moveLabel() positions both elements at the center of the screen. But this changes when
}))
button.translatesAutoresizingMaskIntoConstraints = false the user presses the button and the moveLabel() method is executed. This
view.addSubview(button) method checks the current text assigned to the label and compares it to
the strings "Right", "Left", and "Center" to establish the current position.
let constraintButton1 = NSLayoutConstraint(item: button, attribute: .top, relatedBy: .equal,
toItem: mylabel, attribute: .bottom, multiplier: 1, constant: 30) Then, it removes and adds the constraints to move the label to the next
place. For example, if the current value is "Right", it means that the label is relation—This property sets or returns the constraint’s relation
at the right side of the main view, so we must move it to the left. The code value. It is an NSLayoutConstraint.Relation enumeration value.
removes the constraintRight constraint, adds the constraintLeft constraint, and multiplier—This property sets or returns the multiplier value. It is a
changes the label’s text to "Left". Figure 6-59 shows the three different CGFloat value.
configurations we see on the screen after the button is pressed.
constant—This property sets or returns the constant value. It is a
CGFloat value.
Figure 6-59: Different constraints for a label

The following example creates a button at the center of the screen that
expands or contracts when it is pressed. The first group of constraints
determine its vertical position (50), horizontal position (centerX), and height
 (50), while the last constraint determines its width. The value of the Width
constraint is changed by the expandButton() method every time the button is
Besides adding and deleting constraints, we can also edit their values. For pressed.
this purpose, the NSLayoutConstraint class offers the following properties.
Listing 6-5: Updating constraints
firstItem—This property sets or returns a reference to the first 
element associated with the constraint. It is an optional of type import UIKit
AnyObject.
class ViewController: UIViewController {
secondItem—This property sets or returns a reference to the second var button: UIButton!
var buttonTitle = "Expand"
element associated with the constraint. It is an optional of type var constraintWidth: NSLayoutConstraint!
AnyObject.
override func viewDidLoad() {
firstAttribute—This property sets or returns the value of the first super.viewDidLoad()
element’s attribute. It is an NSLayoutConstraint.Attribute enumeration button = UIButton(configuration: .filled(), primaryAction: UIAction(handler: { [unowned self]
action in
value. self.expandButton()
}))
secondAttribute—This property sets or returns the value of the button.configurationUpdateHandler = { [unowned self] button in
second element’s attribute. It is an NSLayoutConstraint.Attribute var config = button.configuration
enumeration value. config?.title = buttonTitle
button.configuration = config
priority—This property sets or returns the constraint’s priority value. }
button.translatesAutoresizingMaskIntoConstraints = false
It is a structure of type UILayoutPriority with the properties required, view.addSubview(button)
defaultHigh, defaultLow, and fittingSizeLevel.
let constraintButton1 = NSLayoutConstraint(item: button!, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1, constant: 50)

let constraintButton2 = NSLayoutConstraint(item: button!, attribute: .height, relatedBy: .equal, leadingAnchor—This property returns an object that represents the
toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 50)
let constraintButton3 = NSLayoutConstraint(item: button!, attribute: .centerX, relatedBy: .equal, view's leading edge.
toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
view.addConstraints([constraintButton1, constraintButton2, constraintButton3])
trailingAnchor—This property returns an object that represents the
view's trailing edge.
constraintWidth = NSLayoutConstraint(item: button!, attribute: .width, relatedBy: .equal, toItem:
nil, attribute: .notAnAttribute, multiplier: 1, constant: 150) widthAnchor—This property returns an object that represents the
view.addConstraint(constraintWidth) view's width.
}
func expandButton() { heightAnchor—This property returns an object that represents the
if constraintWidth.constant < 280 { view's height.
buttonTitle = "Contract"
constraintWidth.constant = 280 centerXAnchor—This property returns an object that represents the
} else {
buttonTitle = "Expand"
view's horizontal center.
constraintWidth.constant = 150 centerYAnchor—This property returns an object that represents the
}
} view's vertical center.
}

leftAnchor—This property returns an object that represents the
view's left edge.
IMPORTANT: We cannot only modify the constraints added from rightAnchor—This property returns an object that represents the
code but also those created in the Storyboard. All we need to do is view's right edge.
to control-drag a line from the constraint to the view controller to firstBaselineAnchor—This property returns an object that
create an Outlet. This Outlet is like any other we have created so far, represents the baseline of the top line of text in the view.
but instead of referencing a view it references the NSLayoutConstraint
lastBaselineAnchor—This property returns an object that
object that represents the constraint.
represents the baseline of the last line of text in the view.
Creating one constraint at a time with NSLayoutConstraint objects takes
These properties contain objects defined by subclasses of the
time and requires several lines of code. Apple provides two solutions to
this problem: a visual format and layout anchors. The latter is the most NSLayoutAnchor class called NSLayoutXAxisAnchor, NSLayoutYAxisAnchor, and
NSLayoutDimension. The following are the most frequently used methods
popular nowadays because it declares the constraints using properties. The
following are the properties defined in the UIView class for this purpose. included in these classes to add the constraints.

topAnchor—This property returns an object that represents the constraint(equalTo: NSLayoutAnchor, constant: CGFloat)—This
view's top edge. method returns an NSLayoutConstraint object with a constraint that
defines one anchor as equal to the other anchor, and with an offset
bottomAnchor—This property returns an object that represents the
determined by the constant argument.
view's bottom edge.
constraint(greaterThanOrEqualTo: NSLayoutAnchor, constant: view and implements the constraint(equalTo:, constant:) method to add Space
CGFloat)—This method returns an NSLayoutConstraint object with a constraints to every side.
constraint that defines one anchor as greater than or equal to the
other anchor, and with an offset determined by the constant Listing 6-6: Attaching constraints to the view’s anchors
argument. 
import UIKit
constraint(lessThanOrEqualTo: NSLayoutAnchor, constant:
CGFloat)—This method returns an NSLayoutConstraint object with a class ViewController: UIViewController {
constraint that defines one anchor as less than or equal to the other override func viewDidLoad() {
super.viewDidLoad()
anchor, and with an offset determined by the constant argument. let grayView = UIView(frame: CGRect.zero)
grayView.backgroundColor = UIColor.systemGray4
constraint(equalTo: NSLayoutDimension, multiplier: CGFloat) grayView.translatesAutoresizingMaskIntoConstraints = false
—This method returns an NSLayoutConstraint object with a constraint view.addSubview(grayView)
that defines the size attribute of the view equal to the specified
let constraint1 = grayView.leadingAnchor.constraint(equalTo: view.leadingAnchor,
anchor multiplied by the value of the multiplier argument (used to constant: 0)
defined equal widths and heights or to create Aspect Ratio let constraint2 = grayView.trailingAnchor.constraint(equalTo: view.trailingAnchor,
constant: 0)
constraints). let constraint3 = grayView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0)
constraint(equalToConstant: CGFloat)—This method returns an let constraint4 = grayView.bottomAnchor.constraint(equalTo: view.bottomAnchor,
constant: 0)
NSLayoutConstraintobject with a constraint that defines the size view.addConstraints([constraint1, constraint2, constraint3, constraint4])
attribute of the view equal to the value of the equalToConstant }
}
argument. 
constraint(greaterThanOrEqualToConstant: CGFloat)—This
method returns an NSLayoutConstraint object with a constraint that The constraints in this example are created from the Leading anchor of our
defines the size attribute of the view greater than or equal to the view to the Leading anchor of the main view. The same for the Trailing
value of the greaterThanOrEqualToConstant argument. anchor, the Top anchor and the Bottom anchor. Finally, the constraints are
constraint(lessThanOrEqualToConstant: CGFloat)—This added to the view with the addConstraints() method, as we did before.
method returns an NSLayoutConstraint object with a constraint that Because we use a constant of value 0, the view expands to fill the main
defines the size attribute of the view less than or equal to the value of view's area.
the lessThanOrEqualToConstant argument.
Figure 6-60: View pinned from its anchors
Depending on the types of constraints we need, we may use one method
or another. For example, the following view controller creates an empty

}
}

In this example, we assign the value true to the isActive property of each
constraint using dot notation. The NSLayoutConstraint object is created first
with the constraint() method and then the value true is assigned to the
constraint’s property, activating the constraint. The code creates two
Alignment constraints to position a view at the center of the main view and
Width and Height constraints to determine its size. The result is shown
 below.

The NSLayoutConstraint class offers a Boolean property called isActive to Figure 6-61: Constraints activated with the isActive property
activate or the deactivate a constraint. Using this property, we can tell the
system which constraints are going to affect the layout, and because the
process of activating or deactivating a constraint automatically calls the
addConstraint() and removeConstraint() methods, we don't have to call these
methods anymore, as shown in the following example.

Listing 6-7: Adding constraints with the isActive property



import UIKit

class ViewController: UIViewController {


override func viewDidLoad() { 
super.viewDidLoad()

let grayView = UIView(frame: CGRect.zero) The constraints not only can be attached to the anchors of the views but
grayView.backgroundColor = UIColor.systemGray4 also to the anchors of layout guides like the Safe Area. The following are
grayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(grayView)
some of the properties provided by the UIView class to access these layout
guides.
grayView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0).isActive =
true
grayView.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0).isActive = safeAreaLayoutGuide—This property returns a UILayoutGuide object
true with anchor properties we can use to define constraints between the
grayView.widthAnchor.constraint(equalToConstant: 125).isActive = true
grayView.heightAnchor.constraint(equalToConstant: 125).isActive = true views and the Safe Area.
readableContentGuide—This property returns a UILayoutGuide The code in Listing 6-8 defines a constant called safeGuide to store a
object with anchor properties we can use to define constraints reference to the Safe Area's Layout Guide, and then defines the constraints
between the views and the readable area of their superviews (the between the view’s anchors and the anchors of the guide. Because of this,
area in which the lines of text are short enough to be readable). the view does not overlap the status bar.
keyboardLayoutGuide—This property returns a
object (a subclass of UILayoutGuide) with anchor
UIKeyboardLayoutGuide
Figure 6-62: Constraints between a view and the Safe Area's Layout Guide
properties we can use to define constraints between the views and
the area occupied by the keyboard.

Layout guide objects include the same properties as UIView objects to


define the anchors and therefore we can use them to create the
constraints. The following example creates constraints between a view and
the Safe Area.

Listing 6-8: Attaching constraints to the Safe Area's Layout Guide



import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()

let grayView = UIView(frame: CGRect.zero)
grayView.backgroundColor = UIColor.systemGray4 A very useful layout guide is the one provided for the keyboard. The
grayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(grayView) UIKeyboardLayoutGuide class provides information about the space occupied
by the keyboard. With this guide, we can prevent the interface from being
let safeGuide = view.safeAreaLayoutGuide
grayView.leadingAnchor.constraint(equalTo: safeGuide.leadingAnchor, constant: 0).isActive =
covered or hidden behind the keyboard. For instance, if we have a Text
true View, we want to modify the view’s height to make sure the user can
grayView.trailingAnchor.constraint(equalTo: safeGuide.trailingAnchor, constant: 0).isActive = always see what is typing.
true
grayView.topAnchor.constraint(equalTo: safeGuide.topAnchor, constant: 0).isActive = true
grayView.bottomAnchor.constraint(equalTo: safeGuide.bottomAnchor, constant: 0).isActive = Listing 6-9: Adapting the interface to make room for the keyboard
true
} 
} import UIKit

class ViewController: UIViewController {
override func viewDidLoad() {

super.viewDidLoad()
let commentsView = UITextView(frame: CGRect.zero)
commentsView.backgroundColor = UIColor.systemGray5
commentsView.font = UIFont.preferredFont(forTextStyle: .body)
commentsView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(commentsView)

let keyboardGuide = view.keyboardLayoutGuide


commentsView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16).isActive =
true
commentsView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16).isActive =
true
commentsView.topAnchor.constraint(equalTo: view.topAnchor, constant: 20).isActive = true
commentsView.bottomAnchor.constraint(equalTo: keyboardGuide.topAnchor, constant:
-20).isActive = true
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
}


IMPORTANT: Notice that some constants were defined with
This view controller creates a UITextView object and adds it to the main negative values. This is because of the order of the views associated
view. The Leading, Trailing, and Top constraints are attached to the main with the constraints. For instance, if we assign a Trailing constraint
view, but the view’s bottom is connected to the keyboard’s Top anchor with from the Text View to the main view, the value must be negative for
a space of 20 points. This means that the bottom of the Text View is always the space to be on the left side of the view’s trailing side, but that
going to be 20 points from the top of the keyboard, no matter if the changes if instead we assign the constraint from the main view to
keyboard is open or not. the Text View.

Figure 6-63: Interface adapted to the keyboard


Updating Frames These methods work the same way than the ones for the layout. We must
call the setNeedsUpdateConstraints() method first to tell the system that the

constraints need update, and then the updateConstraintsIfNeeded() method to
ask the system to perform the update.
Usually, the views are updated automatically in every drawing cycle (an
automatic cycle that updates the screen several times per second), but
sometimes we may need to get the current position or size of an element
before this process takes place (e.g., read the frame property of a view
modified by a constraint). To make sure that we get the right values, we
can ask the system to update the interface. The UIView class includes the
following methods for this purpose.

setNeedsLayout()—This method informs the system that the


current layout is invalid and it should be updated in the next drawing
cycle.
layoutIfNeeded()—This method asks the system to update the
layout.

These methods must be called on the view property of the view controller
one after another to update the main view and its content. First we call the
setNeedsLayout() method to tell the system that the current layout is invalid
and then the layoutIfNeeded() method to force it to refresh the interface.
After the methods are executed, we can read any of the element’s
properties, such as frame, and we will get values that reflect what the user
sees on the screen. The UIView class also includes the following methods to
update the constraints.

setNeedsUpdateConstraints()—This method informs the system


that the current constraints need an update.
updateConstraintsIfNeeded()—This method asks the system to
update the constraints.

6.3 Size Classes


The way Auto Layout organizes the interface is by telling the system how
every element should be laid out. In practice, this is only useful when the
space available preserves its original proportions. Things change when we
compare devices with very different screens, such as an iPhone and an
iPad, or the same device in different orientations. In these disparate
conditions, the interface must adapt in a way Auto Layout cannot handle.
To get the design we want in every possible configuration, the values of the 
elements and constraints must be drastically modified, and some of them
even removed or added. To know when to perform these changes, the The combinations presented in Figure 6-64 are the only four possible
system classifies the space available based on the magnitude of the combinations of Size Classes: Compact width and Regular height, Compact
horizontal and vertical dimensions. The size is called Regular if it is big width and Compact height, Regular width and Compact height, and
enough to fit a regular interface or Compact otherwise. This classification Regular width and Regular height. By selecting the appropriate Size Classes
is, of course, arbitrary. Apple’s developers decided what should be for the width and height, it is possible to adapt our interface to any space
considered Compact and what Regular based on the screen’s sizes of the available.
devices currently available in the market.
The Compact and Regular values conform a unit of measurement called
Size Classes (this has nothing to do with the classes defined to create
objects). Because of the rectangular shape of the screen, the interface is
defined by two Size Classes, one for the horizontal space and another for
the vertical space. For example, the Size Classes defining the space
available for iPhones in portrait mode are Compact for their horizontal
space and Regular for their vertical space because in this orientation the
screen’s width is constrained but its height has enough space to display a
normal user interface. Every device is assigned different Size Classes,
depending on the size of their screens and orientations.

Figure 6-64: Size Classes assigned to mobile devices


Adapting Properties
Figure 6-66: Menu to select a combination of Size Classes

Xcode offers tools to introduce modifications for specific Size Classes. For
example, we can add a label to an interface for the Size Classes Compact
width and Regular height and the label will be shown only on iPhones in
portrait mode or on a narrow split view in iPads. With these tools, we
cannot only add or remove elements but also modify some properties and
add, remove, or modify constraints for any combination of Size Classes we
want.
The simplest change we can perform for a Size Class is to modify the value

of a property. Some attributes like the background color or the font size
can be changed from the Attributes Inspector panel. When an element on
After the combination of Size Classes is selected and the Add Variation
the interface is selected, this panel shows the values of its properties. The
button is pressed, a new Font field is added to the Attributes Inspector
properties that can be adapted to a specific Size Class include a + button on
panel. Figure 6-67, below, shows what we see when a font is added for the
the side. Figure 6-65, below, shows the button available for the Font when
Size Classes Compact width and Regular height.
a label is selected.
Figure 6-67: New font size for a label in Compact width and Regular height
Figure 6-65: Adapting a font from the Attributes Inspector panel

When we click on the + button to add a font for a Size Class, Xcode shows a

popup menu to select the combination of Size Classes we want to associate
with the font, along with other values we will introduce later.

In this example, we have defined the font size for the label as 38 points Adapting Constraints
when the interface is presented on iPhones in portrait mode (Compact

width and Regular height). The label is displayed with a size of 38 points in
this configuration and with a size of 20 points anywhere else.
There are three things we can do with constraints: modify their values,
deactivate them, and add new ones. The values are not modified; instead,
Figure 6-68: Different font size in portrait and landscape
new values are added for a specific configuration. For example, if we want
our label to be 50 points from the top when the interface is presented in
iPhones in portrait orientation, we can select the label's Top Space
constraint and add a new value for Compact width and Regular height from
the Attributes Inspector panel (Figure 6-69, number 1).

Figure 6-69: Different value for the constraint in Compact width and
In this example, we have selected a combination of Size Classes to affect a Regular height
very specific configuration, but the system also contemplates the
possibility of adapting the interface for the Size Class corresponding to only
one dimension (horizontal or vertical) without considering the other. The
option is called Any and it is available from the Size Classes menu. For
example, we can select the Size Classes Regular width and Any height to
affect only iPads and large iPhones in landscape orientation.


IMPORTANT: The configuration by default is for the Size Classes
Any width and Any height, which means it will be applied to any
configuration of Size Classes unless another value has been provided
IMPORTANT: The Attributes Inspector panel shows an x button on
the left side of every additional value to remove it. When a value is
for a specific Size Class.
removed, the constraint takes back the value defined for the Size
Classes Any width and Any height.

Modifying values of constraints for specific Size Classes is the same we


have done so far with Auto Layout. To perform more drastic changes, we
must remove and add constraints. The constraints are not really removed
but rather uninstalled. We install a constraint for specific Size Classes and
uninstall it for others. For example, if we want our label to be pinned to the
left side of the main view in iPhones in portrait mode but to appear at the we first add the option for a specific configuration clicking the + button on
center of the screen when the device is in landscape mode, we must the left. The following example adds a configuration for Compact width
uninstall the Leading Space constraint for Compact width and Compact and Compact height.
height and install a Horizontally in Container constraint.
Constraints are uninstalled from the Attributes Inspector panel. The Figure 6-71: Adding the Installed option for a specific configuration
following figure shows this panel after the Leading Space constraint
assigned to the label in our example is selected.

Figure 6-70: Installed option

Once the new option is added, we must uninstall the constraint by clicking
the checked button.

Figure 6-72: Uninstalling a constraint for Compact width and Compact


height

The constraint still exists, but it is not functional in Compact width and
Compact height. This means that in that configuration (small iPhones in
landscape mode), the label now needs new constraints. The new
constraints are added as we did before from the Auto Layout buttons, but
we must install them only for Compact width and Compact height to affect
 the label only on that configuration. After adding a Horizontally in
Container constraint to center the label, we add the Installed option for
The option to uninstall the selected constraint is shown at the bottom of Compact width and Compact height and uninstall the constraint for all the
the panel (Figure 6-70, number 1). The option available by default rest of the configurations, as shown in Figure 6-73.
corresponds to Any width and Any height, which means that the constraint
is applied in any combination of Size Classes. To uninstall this constraint,

Figure 6-73: Horizontal alignment constraint installed only for Compact Install this constraint for the Size Classes Compact width and
width and height Compact height and uninstall it for the rest (Figure 6-73). The label
should be displayed on the left anywhere except for small iPhones in
landscape mode.

Now, there is a Leading Space constraint that is installed for the label in any
configuration except Compact width and Compact height, and a
Horizontally in Container constraint (Center X) that is installed only in
Compact width and Compact height. The result is shown below; the label is
always pinned to the left side except in small iPhones in landscape mode.

Figure 6-74: Different constraints applied to the same label

Do It Yourself: This example assumes that you have created the


previous example with a single label and added the Top and Leading
Space constraints shown in Figure 6-68. Select a small iPhone and
change the orientation to landscape (Figure 5-21, number 2). Select
the label's Leading Space constraint. In the Attributes Inspector
panel, click the + button of the Installed option and add a new
option for the Compact width and Compact height (Figure 6-71).
Click on that option to deactivate it (Figure 6-72). Select the label
and add a Horizontally in Container constraint (Figure 6-74, right).
Adapting Elements Trait Collection
 

The process to add or remove an element is the same we used for Every time the interface is displayed, each element receives a Trait
constraints, we select the element and install it or uninstall it for the Collection object with information about its own space on the screen,
configuration of Size Classes we want to affect. including the horizontal and vertical Size Classes. And this is how we can
recognize the current Size Classes from code. The objects are created from
Figure 6-75: Installed option for views the UITraitCollection class. The instantiation of these objects is automatic, but
we can also create our own using the initializers provided by the class.

UITraitCollection(horizontalSizeClass:
UIUserInterfaceSizeClass)—This initializer creates a UITraitCollection
object with the value specified by the argument. The argument is an
enumeration with the values unspecified, compact, and regular.
UITraitCollection(verticalSizeClass: UIUserInterfaceSizeClass)
—This initializer creates a UITraitCollection object with the value
specified by the argument. The argument is an enumeration with the
 values unspecified, compact, and regular.

These initializers are useful when we need to set a configuration for a


specific Trait Collection from code, but more useful are the properties
provided by the class to read the object's values.

horizontalSizeClass—This property returns the value of the


horizontal Size Class represented by the UITraitCollection object. It is an
enumeration of type UIUserInterfaceSizeClass with the values unspecified,
compact, and regular.

verticalSizeClass—This property returns the value of the vertical


Size Class represented by the UITraitCollection object. It is an
enumeration of type UIUserInterfaceSizeClass with the values unspecified,
compact, and regular.

userInterfaceIdiom—This property returns a UIUserInterfaceIdiom horizontal = "Regular"


case .compact:
enumeration value to indicate the platform the app is running on. The horizontal = "Compact"
values available are phone, pad, tv, carPlay, mac, and unspecified. default:
break
displayScale—This property returns a CGFloat value that represents }
switch traitCollection.verticalSizeClass {
the screen's scale. case .regular:
vertical = "Regular"
case .compact:
Views and view controllers conform to a protocol called UITraitEnvironment
vertical = "Compact"
that defines a property to store the UITraitCollection object and a method to default:
report changes. break
}
if horizontal != nil && vertical != nil {
traitCollection—This property returns a UITraitCollection object with print("The configuration is \(horizontal!) width and \(vertical!) height")
}
the current Trait Collection associated to the view or the view }
controller. }

traitCollectionDidChange(UITraitCollection?)—This method is
called by views and view controllers that conform to the Every time the Size Classes assigned to the main view change, the system
UITraitEnvironment protocol every time their Trait Collection changes.
calls the traitCollectionDidChange() method on its view controller. From this
The argument represents the previous trait collection. method, we can read the information of the current trait collection from
the traitCollection property, as we did in Listing 6-10, or get information
We can access this property and method from every view controller and about the previous Trait Collection from the previousTraitCollection parameter
our views. The following example overrides the traitCollectionDidChange() (to know about the previous conditions of the space occupied by the view).
method to print on the console the values of the current Trait Collection. The code in Listing 6-10 reads the horizontalSizeClass and verticalSizeClass
properties of the UITraitCollection object and according to their values
Listing 6-10: Detecting changes in the Trait Collection assigns the strings "Regular" or "Compact" to the corresponding variables

(horizontal or vertical). At the end, we use the values of these variables to
import UIKit
print a message with the current configuration on the console.
class ViewController: UIViewController {
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { Do It Yourself: Create a new project. Complete the ViewController
super.traitCollectionDidChange(previousTraitCollection)
class with the code in Listing 6-10 and run the application. Every
var horizontal: String? time the simulator is rotated, the code prints a message on the
var vertical: String?
console to report the current Size Classes. You can rotate the
switch traitCollection.horizontalSizeClass {
case .regular:
simulator from the menu or by pressing the keys Command + and only use the willTransition() method when we have to perform a task
arrows. related to Trait Collections. The following example implements the
viewWillTransition() method to detect rotation in any device.

View controllers also adhere to the UIContentContainer protocol that among


other methods defines two to report when the trait collection is going to Listing 6-11: Detecting rotation
change. 
import UIKit

viewWillTransition(to: CGSize, with: class ViewController: UIViewController {


UIViewControllerTransitionCoordinator)—This method is called override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator) {
to inform the view controller that its view is going to change size. The super.viewWillTransition(to: size, with: coordinator)
to argument specifies the new dimensions of the view, and the with
argument is an object that adheres to the let width = size.width
let height = size.height
UIViewControllerTransitionCoordinator protocol, which defines a few if width > height {
methods to allow us to customize the transition process. print("We are going Landscape")
} else {
willTransition(to: UITraitCollection, with: print("We are going Portrait")
}
UIViewControllerTransitionCoordinator)—This method is called }
to inform the view controller that the Trait Collection is going to }
change. The to argument defines the new Trait Collection, and the 

with argument is an object that adheres to the


The viewWillTransition() method includes the size parameter with the
UIViewControllerTransitionCoordinator protocol, which defines a few
dimensions the main view is going to take after the transition is over. In the
methods to allow us to modify the transition process.
example of Listing 6-11, we get the view's width and height from this
parameter and compare their values to determine the new orientation. We
The viewWillTransition() method is called every time the main view is going to
are going to landscape if the width is greater than the height or to portrait
change to a different size, but the willTransition() method is only called when
otherwise. In this example, we use the viewWillTransition() method to report
the Trait Collection is going to change. For example, when an iPhone
the new orientation, but these methods may be used to perform changes
rotates from portrait to landscape, both methods are called because the
on elements and constraints that are not possible to anticipate in the
size of the view and the Size Classes change from one orientation to
Storyboard.
another. But iPads present a different situation. The size of the main view
The value received by the with parameter in both methods is an object that
differs from portrait to landscape, but the Size Classes are always Regular
conforms to the UIViewControllerTransitionCoordinator protocol. This protocol
(Regular width and Regular height), so the willTransition() method is never
defines a few methods to allow us to customize the transition process. The
called. For this reason, it is better to implement the viewWillTransition()
following is the most relevant:
method every time we need to detect the change in the space available

animate(alongsideTransition: Block?, completion: Block?)— Orientation


This method executes a closure during the transition process and

another after the transition is complete. The alongsideTransition and
completion arguments are the closures we want to execute while the
Auto Layout and Trait Collections tell the application how to present the
transition occurs or when it is over, respectively.
interface on the screen according to the space available and its
characteristics (Compact or Regular), but the app is the one that
The animate() method must be called on the coordinator object inside the
determines the orientations available.
transition methods introduced before every time we want to participate in
The first level of control is provided by the app’s settings. Xcode includes an
the transition process.
entry at the top of the Navigator Area to access an editor where we can
configure our app (Figure 5-12, number 6). When we click this entry, a
Listing 6-12: Performing a task after the transition is over
project editor is shown in the Editor Area. The editor is divided in panels,

with each panel allowing the configuration of different aspects of the
import UIKit
application, including basic things like setting the possible orientations to
class ViewController: UIViewController { instructions on how the code should be compiled. Figure 6-76 shows some
override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator) {
of the panels available.
super.viewWillTransition(to: size, with: coordinator)
Figure 6-76: Configuration panes
coordinator.animate(alongsideTransition: nil, completion: {(context:
UIViewControllerTransitionCoordinatorContext!) in
let mainView = context.containerView
print("\(mainView.frame.width) / \(mainView.frame.height)")

})
}
} The panel we will probably use the most is General (Figure 6-76, number

1). Here, we can set the app’s version, declare the oldest iOS version our
app supports, and control the orientations available, among other things.
In this example, we call the animate() method with a closure for the
Figure 6-77, below, illustrates the section of this panel where we can
completion argument. The closure receives a value that conforms to the
activate and deactivate the orientations.
UIViewControllerTransitionCoordinatorContext protocol, which among other
properties, it provides the containerView property with a reference to the
Figure 6-77: Orientations
view that is causing the transition. In this case, we use this reference to
read the view's width and height properties and print their values on the
console.
our subclass. The UIViewController class includes the following properties for
this purpose.

shouldAutorotate—This computed property returns a Boolean


value that indicates whether we want the main view to autorotate or
not.
supportedInterfaceOrientations—This computed property
returns a value that determines the orientations supported by the
view controller. It returns a property of the UIInterfaceOrientationMask
structure. The properties available are portrait, landscapeLeft,
landscapeRight, portraitUpsideDown, landscape, all, and allButUpsideDown.

The panel shows the four options available (Portrait, Upside Down, These are computed properties that only return a value, but we can
Landscape Left, and Landscape Right). If any of these options is override them in our view controller and make them return a different
deactivated, the interface of our app does not change when the device value to specify the behavior we want.
rotates to that orientation. The options are pre-set by Xcode according to
the selected device (iPhone or iPad). All the orientations are automatically Listing 6-13: Deactivating rotation for a view controller

activated for iPad and Universal, but the Upside Down orientation is
import UIKit
deactivated if we are creating a project for iPhones. This is to avoid the
user making the mistake of answering a call with the phone upside down. class ViewController: UIViewController {
override var shouldAutorotate: Bool {
return false
IMPORTANT: If you want to configure different orientations for }
each device, select only the device you want to modify and perform }

the changes. Once you finish, select again all the devices you want
your application to run in. The setup is preserved in a plist file you
The shouldAutorotate property indicates whether the main view should
can read and modify from the Info panel.
respond to the device’s rotation. When we return the value true from this
property, the main view is rotated in any of the orientations available
These options affect the entire app. The rotation of the initial view
(those activated in the app's settings), but when we return false, the main
controller and every view controller we add later to the Storyboard will be
view is presented in the current orientation and then never rotated.
limited to the options activated in this panel. If we want to define the
Another way to tell the system if the main view should rotate or not is by
orientation for each view controller, we must specify it in the definition of
overriding the supportedInterfaceOrientations property. With this property, we

can select exactly the orientations available for each view controller by
returning a value or a set of values of type UIInterfaceOrientationMask.
CHAPTER 7 - SCROLL VIEWS

Listing 6-14: Allowing only landscape orientation



import UIKit

class ViewController: UIViewController {


override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
}

In this example, we return the value landscape to let the interface adapt to
any of the landscape orientations (left or right). The main view of this view
controller will only adapt when the device is in landscape mode. If we want
to specify two or more possible orientations, we must declare them in a
set (e.g., [.portrait, .landscapeLeft]).

IMPORTANT: The settings defined on the General panel have


precedence over the view controllers. If we deactivate the landscape
modes from the panel, for example, those orientations will not be
available in any view controller, no matter the values returned by
their methods.
7.1 Scroll Views


Scroll Views are views that display content larger than the area they cover.
They allow the user to scroll the content or zoom in and out any part of it.
The UIKit framework provides the UIScrollView class to create these types of
views. A few classes inherit from UIScrollView to take advantage of its
capabilities, like the UITextView class studied in Chapter 5, but Scroll Views
can manage any type of content we need. Figure 7-1 illustrates what the
user sees when scrolling a picture that is larger than the area occupied by
the view.

Figure 7-1: User scrolling through the Scroll View's content area

A Scroll View can be created from code with the UIView initializer or added
to the Storyboard from the option in the Library.

UIScrollView(frame: CGRect)—This initializer creates a Scroll View


with the position and size determined by the frame argument.

Figure 7-2: Scroll View option in the Library

Implementing Scroll Views


To create the interface in Figure 7-3, we must add the Scroll View first and

pin it to the main view with Leading, Trailing, Top and Bottom constraints.
After that, we add the Image View to the Scroll View with the image we
The content of a Scroll View is inside a delimited area called content area.
want to display (in this case, we use the file doll.jpg, available on our
This area is like a canvas and the Scroll View like a window that allows the
website). Finally, we pin the Image View to the Scroll View with Leading,
user to see one part of the canvas at a time (see Figure 7-1). Therefore,
Trailing, Top and Bottom constraints. Because of these constraints, the
there are two areas to define: the area of the Scroll View and the area of
Scroll View's content adopts the size of the image (its intrinsic content
its content. This demands the creation of two sets of constraints, the ones
size), and users can scroll the image to see the part they want.
we need to position and size the view in the interface and those necessary
to declare the size of the scrollable content. The constraints for the Scroll
Figure 7-4: Scrollable Image
View are applied as always, but the constraints for the content must
determine the content's size. For instance, if the content is composed of an
empty view, we must declare constraints that determine the size of the
view (width and height), including all the space around it (left, right, top
and bottom). From these constraints, the Scroll View can calculate the size
of its content and show it on the screen. But not all constraints are always 
necessary. If the content of the Scroll View is an Image View, for example,
only the space constraints around it are necessary, because its size is
Do It Yourself: Create a new project. Add a Scroll View to the scene
determined by the intrinsic content size of the Image View (the size of the
and pin it to the sides of the main view with Space constraints
content will be the same as the size of the image), as shown in the
(Figure 7-3, left). Drag an Image View from the Library to the Scroll
following example.
View. Download the doll.jpg image from our website, add it to the
Figure 7-3: Scroll View with an image Assets Catalog, and assign it to the Image View (Figure 7-3, center).
Pin the image view to the Scroll View with Space constraints.
Remember to click on the arrows to make sure that the destination
of these constraints is the Scroll View (see Chapter 6, Figure 6-21).
You should see the image expand to its original size (Figure 7-3,
right). Run the application and scroll the image.


Customizing Scroll Views zoomScale—This property sets or returns the current scale of the
content. It is of type CGFloat with a default value of 1.0.

maximumZoomScale—This property sets or returns the maximum
Although we could define the whole interface inside a Scroll View, as we possible scale for the content. It is of type CGFloat with a default value
will see at the end of this chapter, working with constraints inside a Scroll of 1.0.
View introduces some complications. For that reason, the Scroll View and minimumZoomScale—This property sets or returns the minimum
its content are usually defined from code. The following are some of the possible scale for the content. It is of type CGFloat with a default value
properties and methods included in the UIScrollView class to manage the of 1.0.
view and its content. zoom(to: CGRect, animated: Bool)—This method zooms the
content to make visible the area specified by the to argument. The
contentSize—This property sets or returns the size of the content animated argument indicates whether the process will be animated or
area. It is a structure of type CGSize. not.
contentOffset—This property sets or returns the x and y scrollRectToVisible(CGRect, animated: Bool)—This method
coordinates that determine the positions of the piece of content the scrolls the content to make visible the area specified by the first
Scroll View is currently showing on the screen. It is a structure of type argument. The animated argument indicates whether the process will
CGPoint, with a value by default of 0, 0. be animated or not.
contentInset—This property sets or returns the content’s padding.
This is the space between the edges of the Scroll View and its content. Scroll Views can also designate a delegate for configuration and to report
It is a structure of type UIEdgeInsets with the properties top, left, bottom, changes on their state. The delegate must conform to the UIScrollViewDelegate
and right, and a value by default of 0, 0, 0, 0. protocol and implement its methods. The following are the most
isScrollEnabled—This property is a Boolean value that determines if frequently used.
scrolling is enabled.
viewForZooming(in: UIScrollView)—This method is called by the
isPagingEnabled—This property is a Boolean value that determines
Scroll View on its delegate to know which view is going to be zoomed
whether the Scroll View considers its content as pages or not.
in or out.
showHorizontalScrollIndicator—This property is a Boolean value
scrollViewDidScroll(UIScrollView)—This method is called by the
that determines whether the horizontal scroll indicator is visible or
Scroll View on its delegate when the user scrolls the content.
not.
scrollViewDidZoom(UIScrollView)—This method is called by the
showVerticalScrollIndicator—This property is a Boolean value that
Scroll View on its delegate when the user zooms the content in or out.
determines whether the vertical scroll indicator is visible or not.

There are two ways to add the Scroll View to the interface from code. We 
can create the UIScrollView object, add it to the main view or a container import UIKit
view, and then define the constraints from code, as explained in Chapter 6.
class ViewController: UIViewController {
But a better alternative is to add a Stack View to the main view and then @IBOutlet weak var stackContainer: UIStackView!
add the Scroll View as the content of the Stack View, as in the following
override func viewDidLoad() {
example. super.viewDidLoad()
let imgView = UIImageView(image: UIImage(named: "doll"))
let imgWidth = imgView.frame.size.width
Figure 7-5: Vertical Stack View to add a Scroll View from code
let imgHeight = imgView.frame.size.height

let mainScroll = UIScrollView(frame: .zero)


mainScroll.contentSize = CGSize(width: imgWidth, height: imgHeight)
mainScroll.addSubview(imgView)

stackContainer.addArrangedSubview(mainScroll)
}
}

The same way we did with constraints, every time we add a Scroll View
from code, we must define the size of the Scroll View and the size of its
content area. Because we are embedding the Scroll View in a Stack View,
its size will be determined by the size of the Stack View in the Storyboard,
but the size of the content area must be defined in code from the contentSize
property. In the example of Listing 7-1, we create an Image View to add to
the content area and then declare the size of the area as the size of the
image (we did not have to declare the frame of the Image View because
the initializer takes the values from the image). After the content area is
ready, we create the UIScrollView object and add the Image View as its
 content with the addSubview() method. Finally, the Scroll View is added to
the Stack View with the addArrangedSubview() method. The result is the same
Once the Stack View is added to the interface, we must connect it to an shown in Figure 7-4.
Outlet in the view controller and then create and add the Scroll View and
its content from code, as shown next. Do It Yourself: Create a new project. Add a Stack View to the main
view and pin it to the sides (Figure 7-5). Create an Outlet for the
Listing 7-1: Adding a Scroll View from code Stack View called stackContainer and complete the view controller with
the code in Listing 7-1. Download the image doll.jpg from our stackContainer.addArrangedSubview(mainScroll)
}
website and add it to the Assets Catalog. Run the application and }
scroll the image. 

The content area of a Scroll View may contain as many views as we want.
The result of the previous example is the same achieved when the Scroll
The following example includes a second image with a logo at the top-left
View was added to the Storyboard (see Figure 7-4), but there is an issue
corner of the area (the image is called logo.png and is available on our
that requires our attention. Despite connecting the Stack View to the top
website).
of the main view, the image appears below the status bar. This is because
by default the Scroll View positions its content inside the Safe Area. To
Listing 7-3: Adding more views to the content area
modify this behavior, the UIScrollVIew class includes the following property.

import UIKit
contentInsetAdjustmentBehavior—This property determines
how the Scroll View is going to adjust the content's offsets. It is an class ViewController: UIViewController {
@IBOutlet weak var stackContainer: UIStackView!
enumeration of type ContentInsetAdjustmentBehavior with the values
automatic, scrollableAxes, never, and always. override func viewDidLoad() {
super.viewDidLoad()

If we want to make sure that the scrollable content is shown full screen, we let imgView = UIImageView(image: UIImage(named: "doll"))
can define this property with the value never, as shown next. let imgWidth = imgView.frame.size.width
let imgHeight = imgView.frame.size.height

Listing 7-2: Showing the content full screen let mainScroll = UIScrollView(frame: .zero)
 mainScroll.contentSize = CGSize(width: imgWidth, height: imgHeight)
mainScroll.addSubview(imgView)
import UIKit
let logoView = UIImageView(frame: CGRect(x: 25, y: 25, width: 249, height: 249))
class ViewController: UIViewController { logoView.image = UIImage(named: "logo")
@IBOutlet weak var stackContainer: UIStackView! mainScroll.addSubview(logoView)

override func viewDidLoad() { stackContainer.addArrangedSubview(mainScroll)


super.viewDidLoad() }
let imgView = UIImageView(image: UIImage(named: "doll")) }
let imgWidth = imgView.frame.size.width 
let imgHeight = imgView.frame.size.height

let mainScroll = UIScrollView(frame: .zero) The example in Listing 7-3 creates the Image View for the logo with a frame
mainScroll.contentSize = CGSize(width: imgWidth, height: imgHeight) in the position 25, 25, and then adds it to the Scroll View. Because the
mainScroll.contentInsetAdjustmentBehavior = .never
mainScroll.addSubview(imgView) image with the logo is added after the image with the doll, it is shown at

the top, but it is fixed at the position 25, 25 in the content area and 
therefore it scrolls along with the rest of the content, as shown below. import UIKit

class ViewController: UIViewController {


Figure 7-6: Two images in the content area @IBOutlet weak var stackContainer: UIStackView!

override func viewDidLoad() {


super.viewDidLoad()
let imgView = UIImageView(image: UIImage(named: "doll"))
let imgWidth = imgView.frame.size.width
let imgHeight = imgView.frame.size.height

 let mainScroll = UIScrollView(frame: .zero)


mainScroll.contentSize = CGSize(width: imgWidth, height: imgHeight)
mainScroll.contentInsetAdjustmentBehavior = .never
As we already mentioned, the area occupied by the Scroll View on the mainScroll.addSubview(imgView)
interface may be different from the area occupied by its content. If we
let logoView = UIImageView(image: UIImage(named: "logo"))
know the size of the content area, we can position and size content using logoView.translatesAutoresizingMaskIntoConstraints = false
coordinates, as we did in the previous example, but this is something mainScroll.addSubview(logoView)

difficult to do when the content has to be positioned and sized relative to logoView.topAnchor.constraint(equalTo: mainScroll.frameLayoutGuide.topAnchor,
the sides of the content area or to the sides of the Scroll View. For these constant: 25).isActive = true
logoView.leadingAnchor.constraint(equalTo: mainScroll.frameLayoutGuide.leadingAnchor,
situations, the UIScrollView class offers two layout guides that allow us to set constant: 25).isActive = true
constraints between the views and the content area or between the views
and the Scroll View. The following are the properties included in the class stackContainer.addArrangedSubview(mainScroll)
}
to access these guides. }

frameLayoutGuide—This property returns a layout guide that


represents the frame of the Scroll View. This example adds the images of the doll and the logo to the Scroll View, as
we did before, but instead of declaring the position and size of the logo
contentLayoutGuide—This property returns a layout guide that
with a CGRect value, we do it with constraints. We define two constraints to
represents the frame of the content area.
pin the view to the top-left corner of the Scroll View, one from the Top
anchor of the view to the Top anchor of the Frame layout guide, and
Using these guides, we can integrate the content of the Scroll View with
another from the Leading anchor of the view to the Leading anchor of the
the rest of the interface. For instance, we can position the logo of our
Frame layout guide.
previous example relative to the sides of the Scroll View, which will keep it
Because the logo is now pinned to the sides of the Scroll View, it always
fixed in place.
keeps its initial position and does not scroll with the rest of the content
anymore, as illustrated below.
Listing 7-4: Pinning content to the frame of the Scroll View
Zooming
Figure 7-7: Content pinned to the Scroll View

Setting the size of the content area gives enough information to the Scroll
View to allow the user to scroll the content, but to zoom in and out we
must define the maximum and minimum scales allowed with the
minimumZoomScale and maximumZoomScale properties, and also conform to the

UIScrollViewDelegate protocol and implement its viewForZooming() method to
declare which view is going to participate in the process.

Listing 7-5: Allowing zooming



import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {


@IBOutlet weak var stackContainer: UIStackView!
var imgView: UIImageView!

override func viewDidLoad() {


super.viewDidLoad()
imgView = UIImageView(image: UIImage(named: "doll"))
let imageWidth = imgView.frame.size.width
let imageHeight = imgView.frame.size.height

let mainScroll = UIScrollView(frame: .zero)


mainScroll.contentSize = CGSize(width: imageWidth, height: imageHeight)
mainScroll.contentInsetAdjustmentBehavior = .never
mainScroll.delegate = self
mainScroll.addSubview(imgView)

mainScroll.minimumZoomScale = 1.0
mainScroll.maximumZoomScale = 4.0

stackContainer.addArrangedSubview(mainScroll)
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgView
}
}

mainScroll.contentInsetAdjustmentBehavior = .never
mainScroll.delegate = self
The view controller in Listing 7-5 conforms to the UIScrollViewDelegate mainScroll.addSubview(imgView)
protocol and declares itself as the delegate of the mainScroll view. It also
let scrollWidth = stackContainer.frame.size.width
declares a minimum scale of 1.0 and a maximum scale of 4.0, and stores a
let scrollHeight = stackContainer.frame.size.height
reference to the Image View in the imgView property to be able to return it let minScale = min(scrollWidth / imageWidth, scrollHeight / imageHeight)
from the viewForZooming() method. When the Scroll View detects that the let maxScale = max(minScale * 4.0, 1.0)

user is trying to zoom in or out, it calls this method on its delegate to know mainScroll.minimumZoomScale = minScale
which view in the content area is going to be affected by the action and mainScroll.maximumZoomScale = maxScale
begins to zoom until a limit is reached. stackContainer.addArrangedSubview(mainScroll)
The zoom scale is considered to be 1.0 at the initial state. If we set absolute }
values for the minimumScale and maximumScale properties when the initial func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgView
image is larger than the area of the Scroll View, as in the previous example, }
the user will not be able to zoom out enough to see the whole image on }

the screen. There are different ways to accommodate these values,
depending on the effects we want to achieve in our application. For
To calculate what should be the minimum scale so the user can zoom out
instance, the following example calculates the minimum scale from the
to see the whole image on the screen, we need to obtain the current
initial size of the image.
values of the Scroll View’s width and height properties. Because the Scroll
View is going to have the same size of the Stack View, we can read the
Listing 7-6: Calculating the minimum and maximum scales values from this view, but the problem is that those values are not updated

when the main view is loaded but during the system’s updating cycles. To
import UIKit
force the system to update the values at the time we need them, we have
class ViewController: UIViewController, UIScrollViewDelegate { to call the main view’s setNeedsLayout() and layoutIfNeeded() methods (see
@IBOutlet weak var stackContainer: UIStackView! Chapter 6). If we do not execute these methods before reading the view’s
var imgView: UIImageView!
frame property, the values returned might be those defined in the
override func viewDidLoad() { Storyboard (this is not always required, it depends on when in the process
super.viewDidLoad()
we try to read the frames).
view.setNeedsLayout() The minimum scale is calculated in Listing 7-6 by dividing one of the Scroll
view.layoutIfNeeded()
View’s dimensions by the same dimension of the Image View. For example,
imgView = UIImageView(image: UIImage(named: "doll")) if we divide the width of the Scroll View by the width of the Image View,
let imageWidth = imgView.frame.size.width we get the scale necessary to let the user zoom out until the image fits the
let imageHeight = imgView.frame.size.height
width of the view. But because we want the user to be able to see the
let mainScroll = UIScrollView(frame: .zero) whole image, we have to calculate the scale for both dimensions and get
mainScroll.contentSize = CGSize(width: imageWidth, height: imageHeight)
the smaller value. We do this by comparing the result of both formulas Pages
with the min function (min(scrollWidth / imageWidth, scrollHeight / imageHeight)). This

will be our minimum scale. From this value we get the maximum scale
multiplying it by 4.
The UIScrollView class includes functionality to split the content area into
pages. The size of the pages is determined by the size of the Scroll View, so
IMPORTANT: If you want the image to be presented from the
when the user swipes a finger on the screen, the current visible portion of
beginning at the minimum scale, you can set the initial zoom scale
the content area is completely replaced by the section that represents the
with the zoomScale property. Add the statement mainScroll.zoomScale =
next page. To activate this mode, we assign the value true to the
minScale to the Scroll View configuration.
isPagingEnabled property and configure the content to represent the virtual
pages. The following is a simple example that presents three images,
spot1.png, spot2.png, and spot3.png, one per page.

Listing 7-7: Organizing the content area in pages



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var stackContainer: UIStackView!

override func viewDidLoad() {


super.viewDidLoad()
let images = ["spot1", "spot2", "spot3"]

view.setNeedsLayout()
view.layoutIfNeeded()
let scrollWidth = stackContainer.frame.size.width
let scrollHeight = stackContainer.frame.size.height

let mainScroll = UIScrollView(frame: .zero)


mainScroll.contentSize = CGSize(width: scrollWidth * CGFloat(images.count), height:
scrollHeight)
mainScroll.contentInsetAdjustmentBehavior = .never
mainScroll.isPagingEnabled = true

var posX: CGFloat = 0


for img in images {
let imgView = UIImageView(frame: CGRect(x: posX, y: 0, width: scrollWidth, height:
scrollHeight))
imgView.image = UIImage(named: img)
imgView.contentMode = .scaleAspectFill

imgView.clipsToBounds = true The UIKit framework includes a special type of control called Page Control
mainScroll.addSubview(imgView) that is particularly useful in these kinds of applications. The control displays
posX = posX + scrollWidth dots on the screen that represent each page available and changes their
}
colors according to the visible page. There is an option available in the
stackContainer.addArrangedSubview(mainScroll)
} Library to add a Page Control to the Storyboard.
}

Figure 7-8: Page Control option in the Library
The Image Views need to be of the size of the Scroll View and positioned
side by side to represent the pages. This is the reason why in Listing 7-7 we
declare the frame of every Image View. The values for the frame’s width and 
height properties are determined by the size of the Scroll View (scrollWidth
and scrollHeight), and the coordinates for the position are calculated The control is created from the UIPageControl class. The class provides the
according to the page the view represents. The first Image View is added at following properties for configuration.
the coordinate 0, 0, but the second Image View is displaced to the right a
distance determined by the width of the Scroll View. To calculate this value,
currentPage—This property sets or returns the number of the
we use the posX variable. In every cycle of the loop, the variable is
current page.
incremented with the value of the scrollWidth property to establish the
horizontal position of the next view. As a result, the images are positioned numberOfPages—This property sets or returns the number of
on the content area one after another, from left to right. pages represented by the control (the number of dots shown on the
Notice that we also set the contentMode of each Image View to scaleAspectFill screen). It is of type Int.
to fill the entire Image View with the picture and the clipsToBounds property hidesForSinglePage—This property is a Boolean value that
to true to make sure that the image will never be drawn outside the view's determines whether the control should be hidden when there is only
boundaries. one page.
pageIndicatorTintColor—This property sets or returns the color of
Do It Yourself: This example assumes that you are still working the dots when the pages they represent are not visible. It is an
with the interface in Figure 7-5. Update the view controller with the optional of type UIColor.
code in Listing 7-7. Download the images spot1.png, spot2.png, and currentPageIndicatorTintColor—This property sets or returns the
spot3.png from our website and add them to the Assets Catalog. Run color of the dot that represents the visible page. It is an optional of
the application. When you scroll the content, the interface should type UIColor.
transition from right to left or left to right, from one image to backgroundStyle—This property sets or returns the style of the
another. control. It is an enumeration defined in the UIPageControl class called
BackgroundStyle. The possible values are automatic (selects the style
according to the state), prominent (shows a full background), and minimal
(shows a minimal background).
preferredIndicatorImage—This property sets or returns a UIImage
object with the image we want to assign to the indicators.
setIndicatorImage(UIImage?, forPage: Int)—This method sets
the image for the indicator of a specific page. The first argument is the
image we want to assign to the indicator, and the forPage argument is
the page for which the indicator is going to be shown.

To add a Page Control to the interface, we could reduce the height of the
Stack View to make room for it or put the control over the Stack View. For
the following example, we have decided to go with the last option and add
the Page Control on top of the Stack View. The control has a gray
background with an opacity of 0.4 to make it translucent.

Figure 7-9: Page Control added to the interface


Do It Yourself: Views that are dragged over a Stack View become


its subviews. This is not the desirable behavior for the interface in
Figure 7-9. What you want is to put the Page Control over the Stack
View, not inside. To achieve this, you can momentarily reduce the
height of the Stack View, drag the Page Control inside the main view,
set its constraints, and then update the frames to move the Stack
View back to its original size, or just uninstall the Stack View until the
Page Control is added and pinned to the sides. Alternatively, you can
drag the Page Control inside the Stack View and then change its
position in the hierarchy from the Document Outline panel. Once the
Page Control is ready, go to the Attributes Inspector panel and
change its background color to gray with an opacity of 40% (Custom
color).

imgView.clipsToBounds = true

The Page Control is configured by default for three pages, but we can mainScroll.addSubview(imgView)
change the initial values from the Attributes Inspector panel. This is usually posX = posX + scrollWidth
}
not necessary since the content to display with this type of control is stackContainer.addArrangedSubview(mainScroll)
loaded dynamically and its properties are configured from the view }
controller, as shown in the following example. func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageWidth = stackContainer.frame.size.width
let getPage = round(mainScroll.contentOffset.x / pageWidth)
Listing 7-8: Calculating the page let currentPage = Int(getPage)

page = currentPage
import UIKit
pageCounter.currentPage = page
}
class ViewController: UIViewController, UIScrollViewDelegate {
}
@IBOutlet weak var stackContainer: UIStackView!

@IBOutlet weak var pageCounter: UIPageControl!

var mainScroll: UIScrollView! To represent the pages on the screen with the Page Control, we not only
var page: Int = 0
have to configure the control but also get the number of the current visible
override func viewDidLoad() { page. The UIScrollView class does not offer any property or method to return
super.viewDidLoad()
this value; we must calculate it ourselves. Fortunately, the delegate
let images = ["spot1", "spot2", "spot3"]
pageCounter.numberOfPages = images.count protocol offers a method called scrollViewDidScroll() that is executed every
pageCounter.pageIndicatorTintColor = UIColor.black time the user scrolls the content. Also, the system automatically updates
pageCounter.currentPageIndicatorTintColor = UIColor.white
the Scroll View's contentOffset property with the current displacement of the
view.setNeedsLayout() content area every time the content is scrolled. By implementing these
view.layoutIfNeeded()
let scrollWidth = stackContainer.frame.size.width
tools, we can calculate the current page. The value is obtained dividing the
let scrollHeight = stackContainer.frame.size.height value of the x property of the contentOffset structure by the size of the page
(the width of the Scroll View). Because the result is a floating-point
mainScroll = UIScrollView(frame: .zero)
mainScroll.contentSize = CGSize(width: scrollWidth * CGFloat(images.count), height: number, we round it, turn it into an integer, and assign it to the Page
scrollHeight) Control's currentPage property to show the current page on the screen.
mainScroll.contentInsetAdjustmentBehavior = .never
mainScroll.isPagingEnabled = true
mainScroll.delegate = self Do It Yourself: Connect the Page Control to an Outlet called
var posX: CGFloat = 0 pageCounter.Complete your view controller with the code in Listing 7-
for img in images { 8 and run the application. The Page Control should indicate the
let imgView = UIImageView(frame: CGRect(x: posX, y: 0, width: scrollWidth, height:
scrollHeight)) current visible page.
imgView.image = UIImage(named: img)
imgView.contentMode = .scaleAspectFill
Letting the user zoom in or out the entire content area when the Scroll mainScroll.delegate = self
View is configured to work with pages would defeat the purpose of pages, var posX: CGFloat = 0
but it is possible to let the user zoom each page individually. Scroll Views for img in images {
let childScroll = UIScrollView(frame: CGRect(x: posX, y: 0, width: scrollWidth, height:
may be created inside other Scroll Views, and we can use this feature to
scrollHeight))
build complex interfaces where a view may have parts of its content childScroll.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
presented with vertical scrolling and other parts with horizontal scrolling. childScroll.contentInsetAdjustmentBehavior = .never
childScroll.minimumZoomScale = 1.0
We can also use this feature to allow zooming a page in or out. All we have childScroll.maximumZoomScale = 4.0
to do is to embed the content of each page inside additional Scroll Views, childScroll.delegate = self
set their maximum and minimum scales, and implement the let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: scrollWidth, height:
viewForZooming() method of the UIScrollViewDelegate protocol to tell the Scroll scrollHeight))
Views that zooming is allowed (see Listing 7-6). imgView.image = UIImage(named: img)
imgView.contentMode = .scaleAspectFill
imgView.clipsToBounds = true
Listing 7-9: Zooming the pages in and out imageViews.append(imgView)

 childScroll.addSubview(imgView)
import UIKit mainScroll.addSubview(childScroll)
posX = posX + scrollWidth
class ViewController: UIViewController, UIScrollViewDelegate { }
@IBOutlet weak var stackContainer: UIStackView! stackContainer.addArrangedSubview(mainScroll)
@IBOutlet weak var pageCounter: UIPageControl! }
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var mainScroll: UIScrollView! let pageWidth = stackContainer.frame.size.width
var imageViews: [UIImageView] = [] let getPage = round(mainScroll.contentOffset.x / pageWidth)
var page: Int = 0
let currentPage = Int(getPage)
override func viewDidLoad() { if currentPage != page {
super.viewDidLoad() let scroll = imageViews[page].superview as! UIScrollView
let images = ["spot1", "spot2", "spot3"] scroll.setZoomScale(1.0, animated: true)
pageCounter.numberOfPages = images.count page = Int(currentPage)
pageCounter.pageIndicatorTintColor = UIColor.black pageCounter.currentPage = page
pageCounter.currentPageIndicatorTintColor = UIColor.white }
}
view.setNeedsLayout() func viewForZooming(in scrollView: UIScrollView) -> UIView? {
view.layoutIfNeeded() return imageViews[page]
let scrollWidth = stackContainer.frame.size.width }
let scrollHeight = stackContainer.frame.size.height }

mainScroll = UIScrollView(frame: .zero)
mainScroll.contentSize = CGSize(width: scrollWidth * CGFloat(images.count), height:
scrollHeight)
The code in Listing 7-9 creates Scroll Views to embed every Image View
mainScroll.contentInsetAdjustmentBehavior = .never and then adds them as subviews of the mainScroll view. Notice that the
mainScroll.isPagingEnabled = true

minimum and maximum scales are set only for the nested Scroll Views, not pageCounter.currentPageIndicatorTintColor = UIColor.white
for the mainScroll view, because we only want to make the zoom available view.setNeedsLayout()
for the Scroll Views assigned to each page. view.layoutIfNeeded()
There is also an aesthetic change inside the scrollViewDidScroll() method. This
mainScroll = UIScrollView(frame: .zero)
method is executed every time the user scrolls the pages, but the scroll mainScroll.contentInsetAdjustmentBehavior = .never
does not always causes the current page to be replaced. We check this mainScroll.isPagingEnabled = true
mainScroll.delegate = self
condition comparing the current page with the previous one and set the
zoom scale back to 1.0 if the new page is different. This returns the images for img in images {
let childScroll = UIScrollView(frame: .zero)
to the initial state every time the user moves to another page. childScroll.contentInsetAdjustmentBehavior = .never
The examples we have studied so far create applications that let the user childScroll.minimumZoomScale = 1.0
transition from one image to another with the move of a finger. But this childScroll.maximumZoomScale = 4.0
childScroll.delegate = self
presents a problem. Because the size of the pages is determined from the
size of the Scroll View, the application does not work anymore if the device let imgView = UIImageView(frame: .zero)
imgView.image = UIImage(named: img)
is rotated, or the size of the Scroll View changes for some reason. To solve imgView.contentMode = .scaleAspectFit
this issue, we must implement the methods defined for Trait Collections to imgView.clipsToBounds = true
imageViews.append(imgView)
detect and report changes in size (see Chapter 6, Listing 6-10). The
following example implements the viewWillTransition() method to update the childScroll.addSubview(imgView)
frames of the Image Views and their Scroll Views when the device is mainScroll.addSubview(childScroll)
}
rotated. stackContainer.addArrangedSubview(mainScroll)

updateSize()
Listing 7-10: Adapting the content area to a new orientation }
 func updateSize() {
import UIKit let scrollWidth = stackContainer.frame.size.width
let scrollHeight = stackContainer.frame.size.height
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var stackContainer: UIStackView! var posX: CGFloat = 0
@IBOutlet weak var pageCounter: UIPageControl! for imgView in imageViews {
let scroll = imgView.superview as! UIScrollView
var mainScroll: UIScrollView! scroll.frame = CGRect(x: posX, y: 0, width: scrollWidth, height: scrollHeight)
var imageViews: [UIImageView] = [] scroll.contentSize = CGSize(width: scrollWidth, height: scrollHeight)
var page: Int = 0 imgView.frame = CGRect(x: 0, y: 0, width: scrollWidth, height: scrollHeight)
var rotating = false posX = posX + scrollWidth
}
override func viewDidLoad() { mainScroll.contentSize = CGSize(width: scrollWidth * CGFloat(imageViews.count), height:
super.viewDidLoad() scrollHeight)
let images = ["spot1", "spot2", "spot3"]
pageCounter.numberOfPages = images.count let scrollView = imageViews[page].superview as! UIScrollView
pageCounter.pageIndicatorTintColor = UIColor.black mainScroll.contentOffset = CGPoint(x: scrollView.frame.origin.x, y: 0)
} Every time the system detects a rotation or changes on the interface, it
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !rotating { calls the viewWillTransition() method. In this method, we call the animate()
let pageWidth = stackContainer.frame.size.width method of the transition coordinator to update the values of the frames as
let getPage = round(mainScroll.contentOffset.x / pageWidth)
let currentPage = Int(getPage)
soon as the transition is over (the statements were declared inside the
closure for the completion handler). There are two more important things
if currentPage != page { we do inside this method: we restore the zoom level of the current page
let scroll = imageViews[page].superview as! UIScrollView
scroll.setZoomScale(1.0, animated: true) back to its initial scale and modify the value of a Boolean property called
page = Int(currentPage) rotating to inform the rest of the code that the interface is rotating. This is
pageCounter.currentPage = page
}
because we do not want to calculate the number of the current page inside
} the scrollViewDidScroll() method until all the values for the frames are
} updated. The updateSize() method also updates the value of the contentOffset
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageViews[page] property of the mainScroll view at the end to scroll the content to the right
} position after the rotation (when the device is rotated, the value of this
override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator) { property is not updated by the system).
super.viewWillTransition(to: size, with: coordinator)

rotating = true
Do It Yourself: Copy the code you want to try inside your view
coordinator.animate(alongsideTransition: nil, completion: {(context: controller and run the application. In the example of Listing 7-10, we
UIViewControllerTransitionCoordinatorContext!) in
let scroll = self.imageViews[self.page].superview as! UIScrollView defined the value of the contentMode property as scaleAspectFit so the
scroll.setZoomScale(1.0, animated: true) user can see the entire images on the screen. Try other values to find
self.updateSize() out which one is better for your application.
self.rotating = false
})
}
}

An important change introduced in this example is that now the definition


of the frames for the Scroll Views and their Image Views are in a separate
method. This is because every time the size of the mainScroll view changes,
the code must update the values of the frames. We still define the pages
inside the viewDidLoad() method, but we call our updateSize() method at the
end to define their frames. This method is called again when the user
rotates the device.

Scrolling the Interface


Scroll Views allow us to display any content that does not fit entirely on the
screen, including the interface itself. There are times when our interface
contains so many elements and controls that there is not enough space to
show them all, some get hidden when the device is rotated, or are
overlapped by the keyboard. There are different ways to define the 
interface inside a Scroll View, but the simplest is to add a single view inside
the Scroll View and then use this view as the container for the rest of the Do It Yourself: Create a new project. Add a Scroll View to the scene
interface. Figure 7-10 illustrates this process. The Scroll View is added to and expand it to the edges of the main view (Figure 7-10, left). Select
the main view (left), then a single view is added to the Scroll View (center), the Scroll View, open the Pin menu and create Leading, Trailing, Top,
and finally the view is expanded to fill the Scroll View. and Bottom constraints to pin it to the Safe Area. Add an empty view
inside the Scroll View, expand it to occupy the Scroll View’s area
Figure 7-10: Scroll View with an empty view inside
(Figure 7-10, right), and create Leading, Trailing, Top, and Bottom
constraints to pin it to the sides of the Scroll View (make sure that
the destination is the Scroll View). The constraints are shown in red
because the system does not have enough information to determine
the size of the content area yet.

Now that the Scroll View and the container are ready, we must create the
 constraints necessary to determine the size of the scrollable content. The
vertical size is determined by the elements inside the view and their
Both views must be pinned to the edges of their respective containers with constraints, but the horizontal size is determined by the space available.
Top, Bottom, Leading and Trailing Space constraints. The Scroll View can be This requires the creation of two more constraints that pin the container
pinned to the Safe Area and the view inside the Scroll View must be pinned view to the left and right sides of the main view. Figure 7-12 illustrates the
to the edges of the Scroll View, as we did for the Image View in the process. We have to control-drag a line from the view inside the Scroll View
example of Figure 7-3. Figure 7-11, below, shows all the constraints to the main view in the Document Outline panel and create Leading and
required. Trailing constraints with a value of 0.

Figure 7-11: Initial constraints for the views Figure 7-12: Additional constraints for the container view
 

Every time the device is rotated or the space available changes, these These interfaces are usually bigger than the standard size of the scenes.
constraints change the width of the view inside the Scroll View and this Xcode offers the alternative to set a specific size for the scene, so we can
new size is communicated to the Scroll View by the Space constraints design longer interfaces in the Storyboard. The tool is called Simulated Size
added before. and it is available in the Size Inspector panel when the scene is selected. It
includes two options: Fixed and Freeform. When we select Freeform, the
Do It Yourself: Control-drag a line from the view inside the Scroll panel allows us to change the values of the main view’s width and height,
View to the item that represents the main view in the Document as shown next.
Outline panel (Figure 7-12). Create a Leading and a Trailing
constraint with a value of 0. Figure 7-14: Simulated Size tool

So far, we have determined the width of the content area. To determine


the height, we must add the elements of the interface inside the container
view and set constraints that connect them to each side of the view
(including top and bottom). In the following example, we have added a Text

Field, an Image View, a Text View, and a button. Figure 7-13 shows this
interface, including all the required constraints. Notice that we have added
Once all the elements of our interface are added to the container view and
constraints from the elements to every side of their container view,
all the constraints for these elements are defined, the Scroll View is ready
including the bottom, to help the system determine the height of the
to determine the size of its content area and allow us to scroll to see the
interface by adding the values of all the constraints and the heights of the
parts that are not visible, as shown below.
elements.
Figure 7-15: Scrolling the interface
Figure 7-13: Scrollable interface

CHAPTER 8 - GESTURE RECOGNIZERS

Do It Yourself: Select the scene in the Storyboard. Open the Size


Inspector panel and select the Freeform option from the Simulated
Size tool. Set the size of the main view to 320 x 800 or any value you
want. Expand the Scroll View and the container view to fit the new
size. Add the elements shown in Figure 7-13 to the container view,
including the Text View with a gray background. Set the proper
constraints to pin the elements to each other and to the sides of the
view (Figure 7-13). Remember to assign Height or Aspect Ratio
constraints to the elements that do not have intrinsic content size or
need a specific size, such as the image View and the Text View. Run
the application. You should be able to scroll the interface vertically,
as shown in Figure 7-15.
8.1 Gestures of structures) of type UISwipeGestureRecognizerDirection with the
properties right, left, up, and down.

UIPanGestureRecognizer—This class creates a gesture recognizer
Gestures are actions performed by the user on the screen, such as tapping, that recognizes a panning gesture. The class includes the method
swiping, or pinching. These gestures are difficult to detect because all that translation(in: UIView?) to return the current position of the gesture

the screen returns are the position of the fingers, not information on how relative to the starting position, the method velocity(in: UIView?) to
the fingers were moved. This is the reason why Apple introduced gesture return the velocity of the gesture expressed in points per second, and
recognizers. A gesture recognizer is an object that performs all the the properties maximumNumberOfTouches and minimumNumberOfTouches to
necessary calculations to detect a gesture. The UIKit framework defines a set the number of fingers that must touch the view for the gesture to
base class called UIGestureRecognizer to provide the basic functionality for be recognized.
gesture recognizers and multiple subclasses with additional properties and UIScreenEdgePanGestureRecognizer—This class creates a
methods to create recognizers for specific gestures. gesture recognizer that recognizes a panning gesture that starts near
the edge of the screen. The class includes the edges property to
UITapGestureRecognizer—This class creates a gesture recognizer determine the edges that recognize the gesture. The property is a
that recognizes single or multiple taps. The class includes the integer structure of type UIRectEdge with the properties top, left, bottom, right,
properties numberOfTapsRequired and numberOfTouchesRequired to and all.
determine the number of taps that need to occur and the number of UILongPressGestureRecognizer—This class creates a gesture
fingers that have to participate for the gesture to be recognized. recognizer that recognizes a long-press gesture (the user presses one
UIPinchGestureRecognizer—This class creates a gesture or more fingers on the screen for a certain period of time). The class
recognizer that recognizes a pinching gesture (zoom in or out). The includes the properties minimumPressDuration (a Double value to indicate
class includes the properties scale and velocity to set a scaling factor and the minimum time the fingers must press the screen),
return the velocity of the pinch. The properties are of type CGFloat. numberOfTouchesRequired (the number of fingers that must touch the

UIRotationGestureRecognizer—This class creates a gesture screen), numberOfTapsRequired (the number of taps), and
allowableMovement (a CGFloat value that indicates the movement
recognizer that recognizes a rotating gesture. The class includes the
properties rotation and velocity to set the rotation in radians and return allowed).
the velocity in radians per second. They are of type CGFloat.
The UIGestureRecognizer class provides an initializer to create objects from
UISwipeGestureRecognizer—This class creates a gesture every subclass and also general properties and methods. The following are
recognizer that recognizes a swiping gesture. The class includes the
the most frequently used.
properties direction and numberOfTouchesRequired to set the direction
allowed and the number of fingers that must touch the screen for the
init(target: Any?, action: Selector?)—This initializer creates a
gesture to be recognized. The direction property is a structure (or a set
gesture recognizer configured with the target and the action specified

by the arguments. The target argument is the object where the action
will be executed, and the action argument is the method to be
executed when the gesture is detected.
state—This property returns the current state of the gesture
recognizer. It is an enumeration called State included in the
UIGestureRecognizer class. The values available are possible, began, changed,
ended, cancelled, failed, and recognized.

view—This property returns a reference to the view the gesture


recognizer is attached to.
isEnabled—This property sets or returns a Boolean value that 
determines whether or not the gesture recognizer is enabled.
Although gesture recognizers may be added to any of the views provided
numberOfTouches—This property returns the number of touches
by the UIKit framework, not all of them are ready to recognize gestures.
involved in the gesture.
The UIView class offers two Boolean properties we can modify called
location(in: UIView?)—This method returns a CGPoint value that isUserInteractionEnabled and isMultipleTouchEnabled to activate detection of
indicates the coordinates where the gesture occurred in the view gestures and multiple touch. If we want to use gestures in a view that
specified by the in argument. disables these options by default, like an Image View for example, we have
location(ofTouch: Int, in: UIView?)—This method returns a to change their values from code or from the Attributes Inspector panel, as
CGPointvalue that indicates the coordinates where a touch occurred in shown next.
the view specified by the in argument. The ofTouch argument is the
index of the touch we want to check. Figure 8-2: Options to enable interaction

Gesture recognizers may be added to an element from code or in the


Storyboard from the Library. 

Figure 8-1: Gesture Recognizer options in the Library Gesture recognizers are added to the Storyboard as any other element. All
we need to do is to drag the gesture recognizer from the Library and drop
it on the view we want to recognize the gesture. Xcode adds an icon to the
top of the scene to represent it. Figure 8-3 shows what the icon looks like
after we add a Pan Gesture to an Image View on the interface.

Figure 8-3: Gesture Recognizer’s icon


Listing 8-1: Responding to a gesture

 import UIKit

class ViewController: UIViewController {


For this example, we included an Image View with the picture of the husky @IBOutlet weak var picture: UIImageView!
var previous: CGFloat = 0
(husky.png) that we are going to fade in and out responding to the Pan
Gesture. @IBAction func fadingOut(_ sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: picture)
let delta = translation.x - previous
Figure 8-4: Image View with a Pan Gesture let width = picture.frame.size.width
let alpha = picture.alpha + (delta / width)

if alpha > 0.1 && alpha < 1 {


picture.alpha = alpha
}
if sender.state == .ended {
previous = 0
} else {
previous = translation.x
}
}
}

Each gesture returns different kind of information. In the case of the pan
gesture, the object includes a few methods that calculate and return the
current position and velocity of the gesture. Every time a gesture begins, or
a change occurs on its state, the object calls the Action method with a
reference to itself (sender: UIPanGestureRecognizer). From this reference, we can
read and process the information provided by the gesture object and
perform custom tasks. In Listing 8-1, we call the translation() method to get

the value of the current position and calculate the difference between the
old horizontal position and the new one. From this difference and the
The view controller needs an Outlet for the Image View and an Action for
width of the image, we can get a proportional value that added to the
the gesture. To create the Action, we must control-drag a line from the
alpha value allows us to associate the movement of the finger with
gesture’s icon (Figure 8-3) or from the item that represents the gesture in
changes in the translucency of the picture.
the Document Outline panel.

Because the limits of the gesture’s position may vary, we check that the Gesture recognizers may be added to any view from code. The object is
alpha value obtained from those numbers does not go over 1.0 or below created first with the UIGestureRecognizer initializer and then configured and
0.1 (at 0.0 the Image View does not recognize the gesture anymore), and added to the view. The UIView class offers the following methods to manage
then assign it to the alpha property of the Image View to reflect the gesture recognizers.
changes. To keep track of the old position and be able to compare it with
the new one, we store it in the previous property. Every time the action is addGestureRecognizer(UIGestureRecognizer)—This method
called to report changes in the gesture, the new value is stored in this adds a gesture recognizer to the view. The argument is an object of a
property. But because the position of a pan gesture is considered to be 0 at UIGestureRecognizer subclass.
the point when the gesture begins, we cannot keep using the values of a removeGestureRecognizer(UIGestureRecognizer)—This
previous gesture and therefore the value of the previous property must method removes a gesture recognizer from the view. The argument is
return to 0 when the gesture is over. This is detected at the end by reading a reference to the gesture added before with the addGestureRecognizer()
the value of the gesture’s state property. method.

Do It Yourself: Create a new project. Download the husky.png Gesture recognizers implement an old protocol from Objective-C to define
image from our website and add it to the Assets Catalog. Add an the action to perform when the gesture is recognized. This protocol is
Image View to the main view and assign the image of the husky to it. called target/action because it requires the specification of the object that
In the same panel, activate the option called User Interaction is going to respond to the event (the target) and a method that will be
Enabled for the Image View (Figure 8-2). Drag the option Pan executed when the gesture occurs (the action). The target is a reference to
Gesture from the Library and drop it over the Image View. You the object, usually specified as self (the object in charge of the response is
should see the icon pictured in Figure 8-3. Create an Outlet for the the same object that initialized the gesture recognizer) and the method is
image called picture. Control-drag a line from the gesture’s icon to the specified with a selector. Selectors are declared with the instruction
#selector(method) and the syntax for the method must include the names of its
view controller to create an Action called fadingOut(). Complete the
parameters followed by a semicolon, as in #selector(showCounter(sender:)).
view controller with the code in Listing 8-1 and run the application.
Because this is an old Objective-C feature, it also requires the methods to
The picture should fade in and out when you move your finger from
be prefixed by the @objc keyword.
side to side. The following example adds a Tap gesture to a Scroll View to zoom in and
out to specific points of the image. This code assumes that we have added
IMPORTANT: Gesture recognizers cannot be added to different a Stack View to the initial scene and pin it to the Safe Area, as we did for
views. If we want to use the same recognizer for different views, we some of the examples in the previous chapter.
must create new objects, but several gesture recognizers of the
same type may be connected to the same Action. Listing 8-2: Adding gestures from code

import UIKit
and create a method called zoomPicture() to perform the action (Notice that
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var stackContainer: UIStackView! the method was prefixed by the @objc keyword to be able to call it from a
selector).
var mainScroll: UIScrollView!
This example follows the procedure studied in Chapter 7 to configure the
var image: UIImageView!
var zooming = false Scroll View and allow the user to scroll and zoom the picture. When the
Scroll View is ready, the code creates a UITapGestureRecognizer object and
override func viewDidLoad() {
super.viewDidLoad() adds it to the view with the addGestureRecognizer() method. In consequence,
image = UIImageView(image: UIImage(named: "doll")) every time the user taps on the Scroll View, the zoomPicture() method is
mainScroll = UIScrollView(frame: .zero)
executed. In this method, we zoom in or out, depending on the value of
mainScroll.contentSize = CGSize(width: image.frame.size.width, height: the zooming property. To zoom in, the code gets the position of the tap in
image.frame.size.height) the view from the location() method and then zooms in to a rectangle in that
mainScroll.minimumZoomScale = 1.0
mainScroll.maximumZoomScale = 4.0 position. To zoom out, we just set the scale back to 1.0.
mainScroll.delegate = self
mainScroll.addSubview(image)
Do It Yourself: Create a new project. Add a Stack View to the main
let gesture = UITapGestureRecognizer(target: self, action: #selector(zoomPicture)) view and pin it to the Safe Area. Connect the Stack View with an
mainScroll.addGestureRecognizer(gesture)
Outlet called stackContainer. Download the doll.jpg image from our
stackContainer.addArrangedSubview(mainScroll) website and add it to the Assets Catalog. Complete the view
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? { controller with the code in Listing 8-2. Run the application and tap
return image on the image. The image should zoom in the area around your finger.
}
@objc func zoomPicture(sender: UITapGestureRecognizer) {
if !zooming {
let position = sender.location(in: mainScroll)
mainScroll.zoom(to: CGRect(x: position.x, y: position.y, width: 1, height: 1), animated: true)
zooming = true
} else {
mainScroll.setZoomScale(1.0, animated: true)
zooming = false
}
}
}

The UIGestureRecognizer initializer requires two values: a reference to the


object where the action will be performed and the method that is going to
be executed when the gesture is detected. Because we are going to
perform all the tasks on the view controller, we declare the target as self

CHAPTER 9 - NAVIGATION 9.1 Multiple View Controllers


Apps that only require one scene and a simple interface are rare these
days. Because of the limited space, developing apps for mobile devices
demands the creation of multiple scenes to represent virtual screens that
replace one another in response to the user (see Figure 5-18). Practical
applications contain multiple scenes connected with each other following a
predetermined path that users can take to navigate and get the
information they need.
Xcode templates, like the App template we have been using so far to create
our projects, provide initial scenes to start from. The rest of the scenes are
added later to the Storyboard as needed from options included in the
Library. The option to add single scenes that are controlled by
UIViewController objects is called View Controller.

Figure 9-1: View Controller option in the Library

Dragging this option to the Storyboard creates a new scene with its own
main view. After the scenes are added, they can be dragged from the top
bar to different positions to represent what users will see when they
navigate the interface. Figure 9-2 shows what the Storyboard looks like
after we add a new scene and move it to the right side of the initial scene.

Figure 9-2: Second scene added to the Storyboard


Figure 9-3: Templates for new files

There are several options available. The UIViewController class is part of the
 Cocoa Touch API, therefore, to create a file with a UIViewController subclass
for the new scene, we must click on the Cocoa Touch Class option in the
We can add all the scenes we need to build our interface. Every scene Source panel (Figure 9-3, number 1 and 2). Another window opens to
represents a new screen, and their main views will be shown on the insert the name and select the class we want to create our subclass from.
device’s screen one at a time.
Figure 9-4: Creating the subclass
Do It Yourself: Create a new project. Drag the View Controller
option from the Library to the Storyboard. Arrange the scenes to
look like Figure 9-2.

The option to add a new scene to the Storyboard is called View Controller
because it adds a scene that is controlled by an object created from a 
subclass of the UIViewController class, but the view controller is not included
along with the scene, we ought to create the file ourselves. The first value is the name of the class (Figure 9-4, number 1). This value is
Files are created from the File menu. If we click on the File menu and go going to be used as the name of the subclass and the file. It is good
over the New option, a submenu shows the option File. Once we click on practice to start with a word that describes the purpose of the scene and
this option, a window is opened to select the type of file we want to create end with the words "ViewController" to reflect the type of class declared
(we can also use a shortcut pressing the Command + N keys). inside the file (for this example, we called it SecondViewController). The

next option is the superclass of our subclass. For scenes with a single view
this is the UIViewController class.
The last two options determine if the process will create a new scene
(this is not necessary because we have already created the scene in the
Storyboard), and the language Xcode is going to use to create the file (the 
last language we use is always shown by default). After the information is
ready, the file is created and included with the rest of the files in the Now, the second scene has its own view controller, and we are ready to go.
Navigator Area. The procedure to create the interface for this scene and connect it to our
At this point, we have the scene in the Storyboard and the file with its view code is the same as before. We must drag the elements we want to add to
controller, but they are not connected. Xcode does not assign the new file the main view and then connect them to the code in their respective view
to the scene automatically; we must do it ourselves from the Identity controllers (ViewController for the scene on the left and SecondViewController for
Inspector panel. This is one of the configuration panels available in the the scene on the right).
Utilities Area when the scene is selected. To open the panel, select the
scene and click on the fourth button at the top of the Utilities Area (circled Do It Yourself: Create the subclass for the second scene named
in Figure 9-5). SecondViewController.
Select the second scene by clicking on the bar at
the top or on the view controller icon (Figure 5-20). Open the
Figure 9-5: Identity Inspector panel Identity Inspector panel in the Utilities Area (Figure 9-5) and assign
the subclass you have just created to the scene (Figure 9-5, number
1). Open the Assistant Editor (Figure 5-29) and click on the scenes in
the Storyboard to select them. You should see the selected scene on
the left and the corresponding view controller on the right of the
Editor area.

The subclass that controls the scene is specified by the first option (Figure
9-5, number 1). We can write the name of the class or select it from the
list. Figure 9-6, below, illustrates what the panel looks like after we select
the SecondViewController class.

Figure 9-6: Class assigned to the scene


Segues depends on the type of interface we are building. For instance, the Present
Modally and Present As Popover segues are used for small interfaces with

only a few scenes, or when we need to provide additional information,
such as a screen to let the user modify the app's settings. On the other
The scenes must be connected to each other to allow the user to navigate
hand, the Show segue is required when we embed the scenes in a
from one scene to another. For this purpose, the UIKit framework
Navigation Controller, as we will see later.
introduces the UIStoryboardSegue class. From this class, we can create objects
The following example shows how to build a simple interface with Present
called Segues that connect the scenes and perform the transition. There
Modally segues. The interface includes a list of buttons inside the initial
are different types of segues available, as shown next.
scene (the scene pointed by the arrow) and an Image View inside the
second scene. The purpose is to provide buttons in one scene the user can
Show—This segue is used with scenes embedded in a Navigation tap to select a picture, and then transition to a second scene that shows
Controller. It produces a transition from right to left. the selected picture. The second scene also includes a Slider to rate the
Show Detail—This segue is used with scenes embedded in a Split image, as illustrated below.
View Controller. It shows a Detail View (a second scene on the same
screen) when there is enough space available (iPads), or replaces the Figure 9-7: Interface with two scenes
current scene by the new one when the space is limited (iPhones).
Present Modally—This segue presents the next scene over the
current scene. The type of transition is set by the modalTransitionStyle
property of the view controller that triggers the transition. The
possible values are coverVertical (the scene appears from the bottom),
flipHorizontal (the scenes are turned around), crossDissolve (the scenes are
dissolved), and partialCurl (the old scene is curled up to reveal the new
one).
Present As Popover—This segue presents a scene as a popover.
Custom—This segue allows developers to provide a custom
transition.

Segues are fully integrated into the Storyboard and are created by control-
dragging lines between scenes. They can be created from one scene to If we run the app at this point, only the initial scene is shown on the screen
another or from a specific element, such as a button, depending on how and the buttons do not perform any action. To let the user transition from
we want the user to be able to navigate. The type of segue we use also

one scene to another, we must create a segue from the button Show Husky the scene. In this example, we have decided to use a Modal Segue (also
to the second scene. The process is illustrated below. known as Present Modally).

Figure 9-8: Segue from a button to the second scene Figure 9-10: Segue in the Storyboard

The line is control-dragged from the button to the second scene. When the

mouse button is released, Xcode opens a menu to select the type of segue
we want to create.
IMPORTANT: Modal and Popover Segues are implemented in small
applications or to open scenes that present additional information.
Figure 9-9: Popup menu listing the type of segues available
Later in this chapter, we will learn how to create more complex and
professional interfaces with Navigation Controllers and Show segues.

After adding the segue, the Storyboard shows two arrows, one pointing to
the scene on the left (Figure 9-10, number 1) and another connecting the
scenes (Figure 9-10, number 2). The single arrow on the left indicates what
scene is going to be shown first when the app is executed, and the arrow in
between is pointing to the scene that is going to be shown when the segue
is triggered (the button is pressed). The arrows illustrate the path or
possible paths the user can follow while interacting with our application,
 which is the main function of the Storyboard; to help us create a story.

Independent scenes like those implemented in this example can only be Do It Yourself: Add a label and two buttons called Show Husky and
connected using a Modal Segue (Present Modally) or a Popover Segue Show Door to the initial scene. Add a Tinted button called Close, the
(Present As Popover). What segue to implement depends on the label "Rating", a Slider, and an Image View to the second scene.
requirements of our application and the type of information contained in From the Utilities Area, set the value of the Slider to 0, the minimum
to 0, and the maximum to 5. Add the image husky.png to the Assets Transition menu with the types available. Selecting any of these options
Catalog and assign it to the Image View. Apply all the necessary changes the way the destination scene is presented.
constraints to get an interface like Figure 9-7 (remember to assign
higher Content Hugging and Content Compression priorities to the
button or the Image View if necessary). Control-drag a line from the
button Show Husky to the second scene (Figure 9-8). Select the
Present Modally segue. Run your app and press the Show Husky
button. You should see the second scene sliding from the bottom of
the screen to the top.

Modal Segues were introduced for the purpose of connecting single views
and presenting additional information, but still offer a few options for
configuration. Among those options is a list of transitions we can choose
from. The transition by default is called Cover Vertical and it slides the
destination view from bottom to top, but we can change it from the
Utilities Area. When we click on a segue in the Storyboard, the Utilities
Area presents a configuration panel to edit the segue.

Figure 9-11: Configuring the segue

From this panel, we can specify a name to reference the segue from code
(Identifier), set our own custom segue (Class), or select the type of segue,
presentation, and transition we want. The picture on the right shows the

Unwind Segues

Modal Segues allow us to move forward in the Storyboard, but we can also
move backward with a special kind of segue called Unwind Segue. To set up
these segues we must follow a few unconventional steps. First, we must
write an Action on the view controller that presents the next scene, and
then connect to that Action the element in the second scene that triggers 
the Unwind Segue. This is because the view controller responsible for
transitioning back to the previous scene is the one that presented the In Figure 9-12, the Close button inside the second scene is connected to
destination scene. In our example, this is the ViewController class. the Exit icon. When the mouse is released, a menu shows the Actions
So far, we have let Xcode generate the code for the @IBAction, but we can available in the ViewController class.
also write it ourselves and then create the connection, as shown next.
Figure 9-13: Menu to select the Action for the Unwind Segue
Listing 9-1: Adding the Action for the Unwind Segue

import UIKit

class ViewController: UIViewController {


@IBAction func goBack(_ segue: UIStoryboardSegue) {
}
}


The code in Listing 9-1 is an example of how to write the Action in the view
controller that presents the destination scene. The method can have any Once the goBack() Action is selected from this menu, the Close button is
name we want but it must include a parameter of type UIStoryboardSegue to connected to the Action defined before in the ViewController class and the
provide access to the view controller that is been removed. Once this code process is over. The system will remove the scene every time the button is
is ready, we can connect the element in the destination scene to the pressed.
Action. This is done by control-dragging a line from the element to the icon
at the top of the scene called Exit. Do It Yourself: Write the Action in your ViewController class as shown
in Listing 9-1. Control-drag a line from the Close button in the second
Figure 9-12: Exit icon
scene to the Exit button, as shown in Figure 9-12. Select the goBack()
action from the menu (Figure 9-13). Run the application and press The source and destination properties are required depending on the process.
the Show Husky button to present the second scene. Press the Close If we want to access the view controller that is going to be removed, we
button to go back to the initial scene. read the source property (the origin of the transition), but if we want to
access the view controller which scene is going to be presented on the
IMPORTANT: An Unwind Segue can transition back to any scene in screen, we access the destination (the destination of the transition). To
the Navigation Controller that is part of the path the user is illustrate this process, we can add some code to the SecondViewController
following. All we need to do is to define the Action for the view class. This code processes the input from the Slider and adds a property we
controller we want to go back to and then select that Action from can read when the Unwind Segue is triggered.
the menu. The system transitions back to the view controller with
the Action, no matter how many scenes are in between. Listing 9-2: Processing changes in the Slider

The Action in our ViewController class was declared with no statements. This import UIKit

is enough to perform the Unwind Segue, but we can take advantage of this class SecondViewController: UIViewController {
method and its parameter to get information about the view controllers @IBOutlet weak var sliderRating: UISlider!
var rating: Int = 0
that participate in the transition. The UIStoryboardSegue class includes the
following properties for this purpose. override func viewDidLoad() {
super.viewDidLoad()
sliderRating.value = Float(rating)
source—This property returns a reference to the view controller that }
@IBAction func changeRating(_ sender: UISlider) {
is displayed at the beginning of the transition. The value returned is a let value = round(sender.value)
generic UIViewController object that we must cast to the corresponding sliderRating.value = value
rating = Int(value)
type. }
destination—This property returns a reference to the view }

controller that is displayed at the end of the transition. The value
returned is a generic UIViewController object that we must cast to the The code in Listing 9-2 adds an integer property called rating to the
corresponding type. SecondViewController class to keep track of the rating set by the user. The code

identifier—This property returns the string we used in Interface also includes an Outlet for the Slider to set its initial value equal to the
Builder to identify the segue (see Figure 9-11). Using this property, we value of rating and an Action to update the value of this property every time
the Slider is moved (see UISlider in Chapter 5).
know what segue was triggered and can get information about the
Because the Slider works with consecutive floating-point values and our
scene that is going to be shown on the screen.
rating is established by integers between 0 and 5, before assigning the
selected value to the rating property we must round the number to the
nearest integer with the round() function and assign it back to the Slider to

reflect the right rating on the screen. This makes the indicator jump from previously selected by the user, we must send the value of the ratingHusky
one integer to another, helping the user identify the rating that wants to property in the ViewController object back to the SecondViewController object
assign to the picture. every time the Show Husky button is pressed. The UIViewController class
Now that we have the rating property, we can read it from the Unwind provides the following methods for this purpose.
Segue's Action in the ViewController class.
prepare(for: UIStoryboardSegue, sender: Any?)—This method is
Listing 9-3: Reading properties from the source called by the system in the view controller object when a segue is
 triggered and before the transition is initiated. We can override this
import UIKit method in our view controller to access the destination view
class ViewController: UIViewController { controller and modify its properties.
var ratingHusky: Int = 0
performSegue(withIdentifier: String, sender: Any?)—This
@IBAction func goBack(_ segue: UIStoryboardSegue) { method triggers the segue identified with the string specified by the
let controller = segue.source as! SecondViewController
ratingHusky = controller.rating withIdentifier argument. The sender argument is the object that
} triggers the segue (usually declared as self).
}

The method we need to implement to send the information to the second
From the source property of the UIStoryboardSegue object received by the view controller is prepare(). This is one of those methods that are called by
Action, we get a reference to the view controller that is being removed and the system in the view controller and can be overridden to perform custom
access its properties. In our example, this is the SecondViewController class. tasks. In this case, we can override it to access the view controller that is
We get the value, cast it as SecondViewController, and store it in the controller going to be opened and modify its properties before its main view is shown
constant. Now that we have access to the view controller of the second on the screen. The following example expands the code in our ViewController
scene, we read its rating property and store its value in the ratingHusky class to update the value of the rating property in the SecondViewController
property of the ViewController class. Copying the value to a property in the class before the transition is performed.
ViewController object is necessary because the SecondViewController object and
all its properties are destroyed as soon as the scene is removed. Listing 9-4: Sending values to the second view controller before its main
This is the first part of the process; we let the user select a rating and when view is loaded
it moves back to the menu screen, we preserve the selected value in the 
ratingHusky property. If we run the application at this moment and move the import UIKit
Slider to specify a rating, once we go back to the initial scene and tap the
class ViewController: UIViewController {
Show Husky button a second time, the Slider is again in its initial position. var ratingHusky: Int = 0
This is because the value of the Slider is always set as the value of the rating
@IBAction func goBack(_ segue: UIStoryboardSegue) {
property when the main view is loaded. To get the Slider to show the value let controller = segue.source as! SecondViewController
ratingHusky = controller.rating to the Storyboard and connect it to the Show Door button using another
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { Modal Segue, as shown below.
let controller = segue.destination as! SecondViewController
controller.rating = ratingHusky
Figure 9-14: Interface with three scenes
}
}

Now the process is complete. The prepare() method updates the value of the
rating property with the value of the ratingHusky property before the second
scene is shown on the screen, and this value is used by the
SecondViewController object to update the Slider as soon as the main view is
loaded. When the user taps the Close button, the goBack() Action is
executed in the ViewController class and the process is reversed (the 
ratingHusky property is updated with the current value of the rating
property). In consequence, the rating set by the user is always preserved in The process to add the third scene is the same as before; we drag the View
the ratingHusky property inside the view controller of the initial scene and it Controller option to the Storyboard and build the interface. Like the second
is used to update the Slider every time the second scene is opened. scene, this one also needs its own view controller. For this example, we call
it ThirdViewController. Because we use the same type of elements and
Do It Yourself: Copy the code in Listing 9-4 in your ViewController process the same information, the code for this view controller is the same
class. Create an Outlet called sliderRating and an Action called defined for the SecondViewController class in Listing 9-2, including the Outlet
changeRating() for the Slider in the second scene. Complete the and Action for the Slider. What changes is how the ViewController class
SecondViewController class with the code in Listing 9-2. Run the manages the information that is coming from and going to these view
application. The Slider should always reflect the rating set by the controllers. Now we have two segues, one that opens the scene with the
user. husky and another that opens the scene with the door, and therefore the
code in the ViewController class must recognize the segue that is being
IMPORTANT: Never try to access Outlets of one view controller triggered to know how to proceed. This is the reason why the segue’s
from another. Always transfer data through normal properties configuration panel includes an option called Identifier (Figure 9-11). The
because there is no guarantee that the Outlets will be connected to string provided to this option is assigned to the identifier property of the
UIStoryboardSegue object and that is how we know which segue was
the objects in the Storyboard until the main view is fully loaded.
triggered.
The same procedure can be applied to the second button of the menu to As explained before, to change the segue’s configuration we must select it
let the user select another image. All we need to do is to add a new scene and open the Utilities Area (Figure 9-11). For normal segues it is easy, we
select them on the Storyboard and modify their values from the Attributes

Inspector panel, but the only way to select an Unwind Segue is from the let controller = segue.source as! ThirdViewController
ratingDoor = controller.rating
Document Outline panel. The items in this panel that represent Unwind }
Segues are preceded by a round icon, as illustrated below (number 1). }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showHusky" {
Figure 9-15: Unwind segue in the Document Outline panel let controller = segue.destination as! SecondViewController
controller.rating = ratingHusky
} else if segue.identifier == "showDoor" {
let controller = segue.destination as! ThirdViewController
controller.rating = ratingDoor
}
}
}

The prepare() method is executed every time a segue is triggered. The


method in our example compares the value of the identifier property of the
segue object with the strings "showHusky" and "showDoor". If the value is

 equal to "showHusky", it gets the reference to the SecondViewController object


and updates the value of its rating property with the value of the ratingHusky
For our example, we identified the segues that point to the scenes with the property. If, on the other hand, the value of identifier is equal to
strings "showHusky" and "showDoor", and the Unwind Segues with the "showDoor", it gets the reference to the ThirdViewController object and
strings "removeHusky" and "removeDoor", respectively. Once all the updates its rating property with the value of the ratingDoor property. A
segues have been identified, our ViewController class can check these values similar control is performed for the goBack() action, although this time we
and modify the right properties. had to check the identifiers for the Unwind Segues ("removeHusky" and
"removeDoor").
Listing 9-5: Processing values according to the segue
 Do It Yourself: Add a new scene to the Storyboard and recreate
import UIKit the same interface of the second scene but with the image of the
door (you can copy and paste the elements from one scene to
class ViewController: UIViewController {
var ratingHusky: Int = 0 another). Add a segue of type Present Modally from the Show Door
var ratingDoor: Int = 0
button to the new scene. Control-drag a line from the Close button
@IBAction func goBack(_ segue: UIStoryboardSegue) { to the Exit icon to create the Unwind Segue for the third scene.
if segue.identifier == "removeHusky" {
let controller = segue.source as! SecondViewController
Create a file with a subclass of UIViewController called ThirdViewController
ratingHusky = controller.rating and assign it to the third scene. Create an Outlet called sliderRating
} else if segue.identifier == "removeDoor" {
and an Action called changeRating for the Slider and complete the code Segues in Code
of the ThirdViewController class with the code in Listing 9-2. Select the

segues in the Storyboard and assign the identifiers "showHusky" and
"showDoor" (Figure 9-11). Select the Unwind Segues from the Creating a scene for every image available, as we did in the previous
Document Outline panel and assign the identifiers "removeHusky" example, may be enough for small applications but real applications
and "removeDoor" (Figure 9-15). Update the ViewController class with demand some sort of process in which the content of a scene is generated
the code in Listing 9-5. Run the application. The Sliders should dynamically according to the information available. In this scenario, a
always reflect the rating selected by the user for each image. scene provides an updated list of options and a segue connects it to
another scene in charge of presenting the information selected by the user.
IMPORTANT: Unwind Segues are not the only way to transition The segue is therefore not triggered by a specific element on the interface
back to the previous scene. We can also dismiss a modal scene, or but from code by calling the performSegue() method introduced before.
navigate back with the tools provided by Navigation Controllers. We Depending on the number of values available, the list may be presented
will learn how to dismiss a modal scene in Chapter 12 and how to with a Table View, as we will see in Chapter 10, or with controls like pickers.
work with Navigation Controllers later in this chapter. The following example implements a Picker View to present a predefined
list of pictures the user can choose from.

Figure 9-16: Interface to process every option available

The initial scene for this new application contains a Picker View and a
button. When the button is pressed, the code for this view controller
executes the performSegue() method to trigger a segue that transitions to the
second scene. There are different ways to create this segue. The simplest is

to control-drag a line from the view controller's icon to the second scene, ratings = [0, 0]
selectedPicture = 0
as shown below. }
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
Figure 9-17: Creating a segue from one scene to another
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) ->
Int {
return picturesList.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component:
Int) -> String? {
return picturesList[row]
 }
@IBAction func getPicture(_ sender: UIButton) {
selectedPicture = pickerPictures.selectedRow(inComponent: 0)
IMPORTANT: Segues that are not associated with an element must performSegue(withIdentifier: "showPicture", sender: self)
always have an identifier, so they can be referenced from code (see }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
Figure 9-11). For this example, we are going to call it "showPicture". let controller = segue.destination as! SecondViewController
controller.rating = ratings[selectedPicture]
controller.picture = picturesList[selectedPicture]
No matter how the segue is triggered, the system always calls the prepare() }
method before performing the transition. In the following view controller, @IBAction func goBack(_ segue: UIStoryboardSegue) {
let controller = segue.source as! SecondViewController
we use this method to report to the second scene which was the item
ratings[selectedPicture] = controller.rating
selected on the picker. With this information, the second scene loads the }
corresponding image and sets the Slider to display the rating. }

Listing 9-6: Storing the data Most of the code in Listing 9-6 is composed of the delegate methods that

configure the picker (see UIPickerView in Chapter 5), but we also have the
import UIKit
goBack() action implemented before for the Unwind Segue and the prepare()
class ViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource { method to send the information to the second scene when the segue
@IBOutlet weak var pickerPictures: UIPickerView! created in Figure 9-17 is triggered. To store the data, we have created three
var picturesList: [String]!
var ratings: [Int]! properties: picturesList, ratings, and selectedPicture. The picturesList property is an
var selectedPicture: Int! array of strings to store the names of the available pictures, the ratings
override func viewDidLoad() { property is an array of integers to store the rating of each picture, and the
super.viewDidLoad() selectedPicture property is a single integer that stores the index of the
pickerPictures.delegate = self
selected picture. This last property is used to keep track of the picture we
pickerPictures.dataSource = self
are currently working with. When the prepare() method sends the
picturesList = ["Husky", "Door"]
information to the second scene, it takes the values from the picturesList and @IBOutlet weak var sliderRating: UISlider!
@IBOutlet weak var pictureView: UIImageView!
ratings arrays corresponding to the index determined by the selectedPicture var rating: Int = 0
property. Likewise, when the goBack() method processes the rating set by var picture: String!
the user, it stores its value in the position of the ratings array corresponding override func viewDidLoad() {
to the selectedPicture property. super.viewDidLoad()
The interface includes the Show Picture button to select the picture. This sliderRating.value = Float(rating)
pictureView.image = UIImage(named: picture.lowercased())
button has been connected with an Action called getPicture() that gets the }
value selected in the picker, assigns it to the selectedPicture property, and @IBAction func changeRating(_ sender: UISlider) {
let value = round(sender.value)
then executes the performSegue() method to trigger the segue and load the sliderRating.value = value
second scene (the segue was identified with the name "showPicture"). rating = Int(value)
Unwind Segues can also be created from the view controller to the Exit }
@IBAction func goBack(_ sender: UIButton) {
button and then triggered by code. To create an Unwind Segue that is not performSegue(withIdentifier: "goBack", sender: self)
connected to an element, we must control-drag a line from the view }
}
controller's icon at the top of the scene to the Exit button, as shown below. 

Figure 9-18: Creating an Unwind Segue for a view controller Besides triggering the Unwind Segue, the code in Listing 9-7 introduces
additional changes necessary to process the image selected in the initial
scene. The view controller includes a new property to store the name of
the image selected by the user and load its file (picture). This property is
used to load the file, create the UIImage object, and assign it to the Image
View. Notice that the value of the picture property was lowercased to match
 the names of the images.

The view controller for the second scene now has to execute the Do It Yourself: Create a new project. Add a second scene with a
performSegue() method to trigger the Unwind Segue when the user wants to
class called SecondViewController. Design the interface according to
go back to the previous scene. To this end, we have included an Action
Figure 9-16. The initial scene includes a Picker View and a button,
connected to the Close button in the SecondViewController class (the Unwind
and the second scene is the same implemented for previous
Segue was identified with the string "goBack").
examples. Create a Present Modally segue from the initial scene to
Listing 9-7: Showing the data the second scene (Figure 9-17). Assign the identifier "showPicture"
 to this segue. Create an Unwind Segue for the second scene as
import UIKit shown in Figure 9-18. Open the Document Outline panel, select this
segue, and give it the identifier "goBack". Copy the code in Listing 9-
class SecondViewController: UIViewController {

6 into the ViewController.swift file and the code in Listing 9-7 into 9.2 Navigation Controllers
the SecondViewController.swift file. Connect the Slider and the

Image View to their respective Outlets, and the Slider and the Close
button to their respective Actions. Run the application. You should The view controllers used so far are called Content View Controllers
be able to select the pictures from the picker and change their because they manage their own content. They are created from custom
ratings. subclasses of the UIViewController class and can present and control a single
scene. For this reason, these view controllers have limitations when it
comes to replacing their own scene by another or providing the means to
organize the interface. This is the reason why the UIKit framework includes
several subclasses of UIVIewController that introduce better alternatives to
work with multiple scenes. The view controllers created from these classes
are called Container View Controllers because they contain other view
controllers. The most widely used is the Navigation Controller, created
from the UINavigationController class. This class creates a container view
controller that organizes the scenes in sequential order, as shown below.

Figure 9-19: View controllers managed by a Navigation Controller

As shown in Figure 9-19, the scenes managed by a Navigation Controller


are presented in sequence. The first scene opens the second scene, the
second scene opens the third scene, and so on. When the user decides to
go back, the sequence is reversed, the current scene is removed, and the
previous one is shown on the screen. To keep track of the view controllers
in the sequence, the Navigation Controller stores a reference to each one topViewController—This property returns the view controller at
of them in an array called stack and then puts or pulls view controllers the top of the stack (usually the view controller displayed on the
from the stack every time their scenes must be presented or removed. screen).
The possible sequences are defined in the Storyboard, but the stack is
visibleViewController—This property returns the view controller
generated in real time. If we define different paths, as illustrated in Figure
currently shown on the screen. The view controller may belong to the
9-20 below, the Navigation Controller only stores in the stack the view
controllers on the path the user decides to follow. Navigation Controller or to a scene opened modally.
popViewController(animated: Bool)—This method removes the
Figure 9-20: Navigation Controller with multiple paths top view controller from the stack and makes the previous one active.
The animated argument indicates whether the transition will be
animated or not.
pushViewController(UIViewController, animated: Bool)—This
method adds the view controller referenced by the first argument to
the stack. The animated argument indicates whether the transition
will be animated or not.
popToRootViewController(animated: Bool)—This method
 removes all the view controllers from the stack except the initial view
controller. The animated argument indicates whether the transition
The difference between building a structure like this with a Navigation
will be animated or not.
Controller rather than the single view controllers and Modal segues
implemented before is that Navigation Controllers incorporate additional
functionality that makes it easy for us to create a path for the user to
navigate through. They use a segue of type Show that transitions from right
to left (or left to right when the scene is being removed) reflecting on the
screen the sequential order of the scenes in the stack. They also
incorporate bars to identify each scene and their predecessors, and
properties and methods to control the navigation. The following are the
most frequently used.

viewControllers—This property is an array that contains references


to the view controllers currently managed by the UINavigationController
object.

Navigation Controllers in the Storyboard Controller and a segue connects this controller to the initial scene. This
segue is called Root View Controller because it indicates which is the first

scene displayed by the Navigation Controller. When the app is executed,
the system loads the Navigation Controller first and then looks for its root
There are two ways to add a Navigation Controller to the Storyboard. If we
view controller to show the initial scene on the screen.
have already designed the interface, Xcode offers an option to embed the
scenes inside a Navigation Controller. We must select the scene that we
IMPORTANT: If you opt for the option to add the Navigation
want to be the initial scene for the Navigation Controller and then go to
Controller from the Library, there is an additional step you must
the Editor menu at the top of the screen, open the option Embed In, and
follow. When the initial scene is deleted and a new one is added to
select Navigation Controller (Editor/Embed In/Navigation Controller). The
the Storyboard, the arrow pointing to the initial scene disappears. To
other alternative is to drag the Navigation Controller option from the
get back the arrow, you must select the scene you want to be the
Library. This option creates the Navigation Controller along with its initial
initial scene (in this case, the Navigation Controller), go to the
scene. Figure 9-21 illustrates what a single scene looks like after it is
Attributes Inspector panel, and activate the option Is Initial View
embedded in a Navigation Controller.
Controller.
Figure 9-21: Initial scene embedded in a Navigation Controller
More scenes may be added to the interface as before, but instead of using
the Present Modally segue to connect the scenes, we must create segues
of type Show. Figure 9-22, next, illustrates what we see when we add a
second scene and connect it using this segue. The initial scene includes a
label with the text "Root View" and a button with the title "Open Second
View". The button was connected to the second scene with a Show segue
(the first option of the popup menu). When the user presses the button,
the second scene is shown on the screen transitioning from right to left.

Figure 9-22: Navigation stack in the Storyboard

The scene on the left represents the Navigation Controller and the scene

on the right is our initial scene. The arrow is now pointing to the Navigation
The Show segue performs the transition to move from one scene to Navigation Bar
another, but the Navigation Controller takes care of the rest. It adds a

Navigation Bar at the top that we can use to identify the scenes and a Back
button to each scene to allow the user to return to the previous one. All
As we just mentioned, the Navigation Controller introduces a Navigation
the functionality for this button is already implemented by the controller,
Bar at the top of every scene to help the user navigate. Navigation Bars are
so unless we want to customize the process, there is nothing else we need
just empty views. Their content is not generated by the bar itself but by
to do to allow the user to navigate back and forward.
objects of the UINavigationItem class. In turn, these objects are containers for
other views that represent things like the title of the scene and buttons, as
Do It Yourself: Create a new project. Select the scene, open the
shown below.
Editor menu at the top of the screen, go to Embed In, and select the
option Navigation Controller. The Storyboard should look like Figure Figure 9-23: Elements of the Navigation Bar
9-21. Drag the View Controller option from the Library to add
another scene to the Storyboard. Add a label and a button to the
initial scene and a label to the second scene, as illustrated in Figure
9-22 (Make sure that the elements are positioned below the
Navigation Bar at the top). Control-drag a line from the button in the
initial scene to the second scene to create a Show segue. Create a 
file with the view controller for the second scene called
SecondViewController, as we did before. Run the application and press Navigation Items are created automatically by the system along with the
the button. Press the Back button to go back to the initial scene. Navigation Bar, but we can remove them and add them again from the
Library if necessary.

Figure 9-24: Navigation Item option in the Library

Navigation items may include a title, a prompt, and buttons on the sides.
As we have seen in the previous section of this chapter, one button is
automatically generated by the Navigation Controller to help the user
navigate back to the previous scene. By default, this button has the same

title as the previous scene or the title Back if the scene has no title, but we Do It Yourself: Click on the Navigation Bar of the initial scene, open
can define our own, along with the scene's title and prompt, from the the Attributes Inspector panel, and assign the value "Menu" to the
Attributes Inspector panel. Title option and the value "Go Back" to the Back Button option
(Figure 9-25). You should see something like Figure 9-26. You can
Figure 9-25: Configuring the Navigation Item
also double-click the bar to change the title from the Storyboard.

IMPORTANT: If a title is provided but no value is defined for the


Back button, the button adopts the title of the scene (the Back
button in our example would have the title "Menu").

 Of course, the items of a UINavigationItem object may be modified from code.


The UIViewController class offers a property called navigationItem to access the
The values inserted in the first two fields (Title and Prompt) are shown in Navigation Item and its properties. The following are the most frequently
the current scene, but the value of the third field corresponds to the title used.
of the Back button the system is going to show in the next scene to go back
to this one. For example, if we select the Navigation Item of the initial title—This property sets or returns the title. It is an optional of type
scene in our interface and insert the title "Menu" for the bar and the title String.
"Go Back" for the button, we will see something like Figure 9-26, below. prompt—This property sets or returns the prompt. It is an optional
The scene's title is shown at the center of the Navigation Bar and the Back of type String.
button in the second scene now reads "Go Back".
largeTitleDisplayMode—This property sets or returns a value of an
enumeration called LargeTitleDisplayMode included in the UINavigationItem
Figure 9-26: Custom Navigation Items
class that determines whether the Navigation Bar will show large
titles or not. The values available are automatic (it is determined by the
system depending on the previous scene), always, and never.
backBarButtonItem—This property sets the Back button for the
scene. It is an optional of type UIBarButtonItem.
hidesBackButton—This property is a Boolean value that determines
 whether the Back button is visible. The property must be defined in
the scene that shows the button.
The following is the view controller assigned to the second scene of our
example. When the view is loaded, we modify the value of the title property Figure 9-28: Navigation Bar with scrollable content
of the Navigation Item to change the scene's title to "Second".

Listing 9-8: Assigning the scene's title from code



import UIKit

class SecondViewController: UIViewController {


override func viewDidLoad() { 
super.viewDidLoad()
navigationItem.title = "Second"
} When the bar is presented in a Compact vertical Size Class, the height of
} the bar is reduced to make room for the content. In this case, the bar can

present a different appearance.
Figure 9-27: Scene's title defined from code
Figure 9-29: Navigation Bar in landscape mode

 

The Navigation Bar was designed to work with scrollable content and The Navigation Bar is created from an object of the UINavigationBar class,
therefore it adapts to the content’s offset. If the content is not scrolled (the which includes properties to modify the appearance of the bar in all these
top of the content is visible to the user), the bar presents a scroll edge conditions.
appearance, but if the content has been scrolled, the bar presents a
standard appearance. By default, the scroll edge appearance is standardAppearance—This property sets or returns the
transparent, and that’s why we don’t see any bar at the top of our scenes appearance of the Navigation Bar when it is displayed with a standard
in the Storyboard, but in the standard appearance the bar is displayed in a height.
translucent gray, so we can see it when the content is scrolled. Figure 9-28
shows what we see when the scene contains a table, and the table is compactAppearance—This property sets or returns the
scrolled (We will study Table Views in Chapter 10). appearance of the Navigation Bar when it is displayed with a compact

height. following initializer to convert an AttributeContainer structure into this type of


scrollEdgeAppearance—This property sets or returns the dictionary.
appearance of the Navigation Bar when it is displayed in a standard
Dictionary(AttributeContainer, including:
height and the content is not scrolled.
AttributeScope.Type)—This initializer creates a dictionary with the
compactScrollEdgeAppearance—This property sets or returns the attributes defined by the AttributeContainer structure according to the
appearance of the Navigation Bar when it is displayed in a compact scope specified by the including argument.
height and the content is not scrolled.
The UINavigationBarAppearance class inherits from the UIBarAppearance class,
The appearance is defined by a UINavigationBarAppearance object. The class which includes additional properties for configuration. The following are
includes properties for configuration. The following are the most the most frequently used.
frequently used.
backgroundColor—This property sets or returns the bar's
titleTextAttributes—This property sets or returns the attributes of background color.
the text for the title. The value is a dictionary with NSAttributedString
backgroundImage—This property sets or returns the bar's
keys.
background image.
largeTitleTextAttributes—This property sets or returns the
backgroundImageContentMode—This property sets the
attributes of the text for large titles. The value is a dictionary with
background image content mode. The possible values are scaleToFill,
NSAttributedString keys.
scaleAspectFit, scaleAspectFill, redraw, center, top, bottom, left, right, topLeft,
titlePositionAdjustment—This property sets or returns the topRight, bottomLeft, and bottomRight.
position of the title. It is an object of type UIOffset, which includes the
backgroundEffect—This property sets or returns the bar's blur
horizontal and vertical properties to set the title's horizontal and vertical
effect. It is an optional of type UIBlurEffect. The value nil defines no
offsets.
effect.
setBackIndicatorImage(UIImage?, transitionMaskImage:
UIImage?)—This method sets the image for the back button. The The UIViewController class offers a property called navigationController to get a
arguments are the images we want to assign to the button in the reference to the Navigation Controller the view controller belongs to. In
normal and transition mode. turn, the UINavigationController class includes the navigationBar property with a
reference to the UINavigationBar object that manages the Navigation Bar.
The titleTextAttributes and largeTitleTextAttributes properties works with a From these properties, we can access the bar from our view controllers
dictionary of NSAttributedString keys. The Dictionary structure includes the and define the appearance, as in the following example.
Listing 9-9: Configuring the Navigation Bar for standard and compact keys, so we had to use the Dictionary initializer to convert the
heights AttributeContainer structure to this format.
 Because we assigned different configurations for the standard and compact
import UIKit heights, the Navigation Bar changes appearance every time the Size Classes
change, as it happens when an iPhone rotates from portrait to landscape.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad() Figure 9-30: Different appearances for standard and compact bars
var container = AttributeContainer()
container.font = UIFont.preferredFont(forTextStyle: .title1)
container.foregroundColor = UIColor.white

let standard = UINavigationBarAppearance()


standard.backgroundColor = UIColor.systemRed
if let keys = try? Dictionary(container, including: \.uiKit) {
standard.titleTextAttributes = keys
}
let compact = UINavigationBarAppearance() 
compact.backgroundColor = UIColor.yellow

let bar = navigationController?.navigationBar Do It Yourself: Update the ViewController class with the code in
bar?.scrollEdgeAppearance = standard
bar?.compactScrollEdgeAppearance = compact Listing 9-9. Run the application. You should see a red bar (Figure 9-
} 30, left). Rotate the device to landscape. Now the bar should be
}
 compact and yellow (Figure 9-30, right).

The standard and compact appearances are only applied when the content The UINavigationController class also includes properties and methods to
is scrolled. If we don't have scrollable content, as in this case, only the assign some functionality to the Navigation Bar.
scroll edge appearance is applied and that's all we need to configure the
bar. The view controller in Listing 9-9 creates two UINavigationBarAppearance hidesBarsOnTap—This property takes a Boolean value that
objects, one for the standard and another for the compact appearance. indicates whether the bars should be hidden when the user taps on
The configuration for the standard appearance includes a red background the screen.
while the background for the compact appearance is defined yellow. Once
the UINavigationBarAppearance objects are ready, we assign them to the bar's
hidesBarsOnSwipe—This property takes a Boolean value that
scrollEdgeAppearance and compactScrollEdgeAppearance properties to change the
indicates whether the bars should be hidden when the user swipes
configuration for each mode. the finger on the screen.
Notice that the attributes for the title are defined with an AttributeContainer hidesBarsWhenKeyboardAppears—This property takes a
structure, but the titleTextAttributes property takes a dictionary of attribute Boolean value that indicates whether the bars should be hidden when

the keyboard is visible. bar?.compactScrollEdgeAppearance = compact

hidesBarsWhenVerticallyCompact—This property takes a navigationController?.hidesBarsOnTap = true


}
Boolean value that indicates whether the bars should be hidden when }
the vertical Size Class is Compact. 

isNavigationBarHidden—This property returns a Boolean value Figure 9-31: Navigation Bar removed when the user taps the screen
that indicates if the Navigation Bar is currently hidden.
setNavigationBarHidden(Bool, animated: Bool)—This method
hides or shows the Navigation Bar. The first argument indicates
whether the bar will be hidden, and the animated argument
determines if the process is going to be animated.

These are Boolean properties used to activate or deactivate Navigation Bar 


features. For instance, we can assign the value true to the hidesBarsOnTap
property to allow the user to hide or show the bar by tapping on the In addition to the bar, we can also modify the appearance of the buttons.
screen. The UINavigationBarAppearance class defines the following properties for this
purpose.
Listing 9-10: Setting up Navigation Bar features
 buttonAppearance—This property sets or returns the appearance
import UIKit of the buttons. It is an appearance object of type
UIBarButtonItemAppearance.
class ViewController: UIViewController {
override func viewDidLoad() { backButtonAppearance—This property sets or returns the
super.viewDidLoad()
var container = AttributeContainer() appearance of the back button. It is an appearance object of type
container.font = UIFont.preferredFont(forTextStyle: .title1)
UIBarButtonItemAppearance.
container.foregroundColor = UIColor.white

let standard = UINavigationBarAppearance() The UIBarButtonItemAppearance class defines the properties normal, disabled,
standard.backgroundColor = UIColor.systemRed
if let keys = try? Dictionary(container, including: \.uiKit) { highlighted, and focused to set the appearance for the buttons on every state.
standard.titleTextAttributes = keys To change the appearance of the Navigation Bar buttons, we must create
}
let compact = UINavigationBarAppearance()
the UIBarButtonItemAppearance object, then modify the properties for the
compact.backgroundColor = UIColor.yellow state we want to change, and finally assign the object to one of the
appearance properties (buttonAppearance or backButtonAppearance). The
let bar = navigationController?.navigationBar
bar?.scrollEdgeAppearance = standard
following example illustrates how to change the font of the back button in to the backButtonAppearance property of the UINavigationBarAppearance object
the normal state. and give the button a new image. Now the Back button is shown in big
letters.
Listing 9-11: Configuring the back button
 Figure 9-32: Custom appearance for the back button
import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
var container = AttributeContainer()
container.font = UIFont.preferredFont(forTextStyle: .title1)

let buttonAppearance = UIBarButtonItemAppearance()


if let textAttributes = try? Dictionary(container, including: \.uiKit) { 
buttonAppearance.normal.titleTextAttributes = textAttributes
}
let standard = UINavigationBarAppearance()
standard.backgroundColor = UIColor.systemGray4
standard.backButtonAppearance = buttonAppearance
standard.setBackIndicatorImage(UIImage(systemName: "arrow.backward.circle"),
transitionMaskImage: UIImage(systemName: "arrow.backward.circle"))

let compact = UINavigationBarAppearance()


compact.backgroundColor = UIColor.yellow

let bar = navigationController?.navigationBar


bar?.scrollEdgeAppearance = standard
bar?.compactScrollEdgeAppearance = compact
}
}

The first thing we do in the view controller of Listing 9-11 is to create the
AttributeContainer structure with the attributes we want to assign to the
button's text. In this case, we define a dynamic font of type title1. Then, we
initialize the UIBarButtonItemAppearance object to configure the appearance
for the button. Depending on the state of the button we want to affect, we
must apply the appearance to the right property. In our example, we
modify the attributes of the text for the normal property, which affects the
button's normal state. When the button's appearance is ready, we assign it

Large Titles property to change this behavior. The option is also available for each
scene from the Attributes Inspector panel.

Figure 9-35: Large Titles option for each scene
Navigation Bars offer two formats to display the titles: small and large.
Small titles are shown by default, as illustrated by the previous examples,
but we can specify our preference using the prefersLargeTitles property
provided by the UINavigationBar class. The option is also available from the
Attributes Inspector panel when we select the Navigation Bar.

Figure 9-33: Prefers Large Titles option


Of course, we can also modify the largeTitleDisplayMode property from code.


The values available are automatic, always, and never. For example, we can
deactivate large titles in the second scene of our example by assigning the
value never to this property.

Listing 9-12: Deactivating large titles
After the property is set to true or the option is selected from the Attributes 
Inspector panel, the Navigation Bars show large titles, as illustrated bellow. import UIKit

Figure 9-34: Large titles class SecondViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Second"
navigationItem.largeTitleDisplayMode = .never
}
}

Now, the initial scene shows large titles, but the second scene displays the
 standard title with a small font.

The prefersLargeTitles property activates large titles for every scene in the Figure 9-36: Large and small titles
navigation stack, but the UINavigationItem class offers the largeTitleDisplayMode
Bar Buttons

Besides the Back button, we can also add custom buttons to the Navigation
Bar. These are not regular buttons; they are created from the
 UIBarButtonItem class with the following initializers.

UIBarButtonItem(title: String?, image: UIImage?,


primaryAction: UIAction?, menu: UIMenu?)—This initializer
creates a bar button with the configuration specified by the
arguments. The title and image arguments are the button's title and
image, respectively. The primaryAction argument specifies the action
the button performs when pressed. And the menu argument specifies
the contextual menu to be presented by the button.
UIBarButtonItem(systemItem: SystemItem, primaryAction:
UIAction?, menu: UIMenu?)—This initializer creates a predefined
bar button. The systemItem argument determines the type of button
we want to create. It is an enumeration called SystemItem included in
the UIBarButtonItem class with the values done, cancel, edit, save, add,
flexibleSpace, fixedSpace, compose, reply, action, organize, bookmarks, search,
refresh, stop, camera, trash, play, pause, rewind, fastForward, undo, redo, and close.
The primaryAction argument defines the action, and the menu
argument defines the contextual menu to be presented by the button.

As always, there is also an option in the Library to add a bar button to a


scene.

Figure 9-37: Bar Button Item option in the Library

The bar buttons may be added to the left or the right side of the bar, but if
we add them to the left in a scene that is not the initial scene, the buttons
replace the Back button. Figure 9-37 shows the interface of our example
after a Bar Button Item was dragged from the Library to the right side of
the Navigation Bar in the initial scene.

Figure 9-38: Bar Button Item in the Navigation Bar
The UINavigationItem class includes the following properties to add the
buttons from code.

leftBarButtonItems—This property is an array of UIBarButtonItem


objects that defines the bar buttons for the left side of the bar.
rightBarButtonItems—This property is an array of UIBarButtonItem
objects that defines the bar buttons for the right side of the bar.
leftItemsSupplementBackButton—This property is a Boolean
value that determines whether the Back button is shown along with
 the items on the left side of the bar or not. By default, the Back
button is not shown when there are custom buttons in its place. We
The title of the button by default is "Item", but we can change it from the can change this behavior by setting this property to true.
Attributes Inspector panel, as well as specify the button's design, add
custom images for portrait and landscape modes, and define a tint color Bar buttons are created from the UIBarButtonItem class, which is a subclass of
(the original colors of the images are ignored). Figure 9-39 shows what we the UIBarItem class. Together, these classes define properties and methods
see when the Camera value of the System Item option is selected. to configure the buttons.

Figure 9-39: Predefined Bar Button Item title—This property sets or returns the button’s title. It is an optional
of type String.
image—This property sets or returns the button's image. It is an 

optional of type UIImage.


The view controller in Listing 9-13 creates a bar button with an SF Symbol
landscapeImagePhone—This property sets or returns the image of a trash can and an action that prints a message on the console. After the
for the button that is going to be shown when the device is in button is created, we change its color to red and assign it to the
landscape mode. It is an optional of type UIImage. rightBarButtonItems property to add it to the Navigation Bar.

tintColor—This property sets or returns the button's tint color.


Figure 9-40: Custom bar button
primaryAction—This property sets or returns the UIAction object
with the action to be performed when the button is pressed.
menu—This property sets or returns a UIMenu object with the
contextual menu to be presented when the button is pressed.

The arguments included in the UIBarButtonItem initializers are optional. For


instance, we can define a button with a title or an image. If the button
contains an image, then the title argument may be ignored. The same
happens with the menu argument, which only applies when we want the
button to open a contextual menu. For instance, the following example

illustrates how to add a simple button with an image to the initial scene in
our project.
Do It Yourself: Erase the button with the camera added before to
the initial scene if necessary. Update the ViewController class with the
Listing 9-13: Adding buttons to the Navigation Bar
code in Listing 9-13. Run the application. You should see a red trash

import UIKit
can at the top (Figure 9-40).

class ViewController: UIViewController {


We can also use a UIBarButtonItem object to define the back button for the
override func viewDidLoad() {
super.viewDidLoad() destination scene. In this case, we only need to specify the button's title,
let button = UIBarButtonItem(image: UIImage(systemName: "trash"), primaryAction: because the action is already determined by the Navigation Controller.
UIAction(handler: { action in
print("Button pressed")
})) Listing 9-14: Replacing the back button
button.tintColor = UIColor.systemRed

navigationItem.rightBarButtonItems = [button] import UIKit
}
} class ViewController: UIViewController {

override func viewDidLoad() { normal buttons if the Navigation Bar is configured with large titles.
super.viewDidLoad()
let button = UIBarButtonItem(title: "Close") All you need to do is to set the Prefers Large Titles option and drag
button.tintColor = UIColor.systemGreen the buttons from the Library to the Navigation Bar.
navigationItem.backBarButtonItem = button
}
}

The button is created first, as before, but then it is assigned to the


backBarButtonItem property of the Navigation Item, effectively replacing the
original back button. Now, every time we open the second scene, the
button reads "Close".

Figure 9-41: Custom back button

Like buttons, bar buttons can also display contextual menus. The
UIBarButtonItem class includes the menu property to associate a menu with
the button. All we need to do is to set the primary action as nil and define
the menu as we did for the UIButton class in Chapter 5.

IMPORTANT: Bar buttons are created from the UIBarButtonItem class


instead of the UIButton class because they were specifically designed
to fit in the narrow space of a Navigation Bar, but you can implement
Toolbar setToolbarItems([UIBarButtonItem]?, animated: Bool)—This
method assigns the bar buttons provided by the first argument to the

toolbar. The animated argument determines if the process will be
In addition to the Navigation Bar, UIKit includes a class called UIToolbar to animated.
add an additional bar to the scene. A Navigation Controller includes a
Toolbar by default, but it is hidden. If we want to see how the Toolbar is For configuration, the UIToolbar class includes the same properties available
going to affect our interface, we can select the Navigation Controller and for Navigation Bars (standardAppearance, compactAppearance, scrollEdgeAppearance,
set the option from the Attributes Inspector panel to show it in the and compactScrollEdgeAppearance). These properties take a UIToolbarAppearance
Storyboard. object. The UIToolbarAppearance class inherits from the UIBarAppearance class
and therefore it provides all the same properties for configuration, but it
Figure 9-42: Option to show the Toolbar in the Storyboard also includes the following property to change the appearance of the
buttons.

buttonAppearance—This property sets or returns the appearance


of the buttons. It is an appearance object of type
UIBarButtonItemAppearance.

The Toolbar is not shown on the screen unless it includes at least one
button. The buttons can be added by dragging the Bar Button Item option
in the Library to the main view, as we did for Navigation Bars, or from code

implementing the methods described above. The following example shows
how to define a Toolbar with a custom appearance and one button.
The Toolbar is not visible in the scenes, but elements pinned to the bottom
of the Safe Area will move up and bar buttons added to the view will be
Listing 9-15: Configuring the appearance of the Toolbar
included in the Toolbar. The buttons are defined by the UIBarButtonItem

class. This is the same class used before to add buttons to the Navigation
import UIKit
Bar. The UIViewController class offers the following property and method to
manage these buttons from code. class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
toolbarItems—This property is an array of UIBarButtonItem objects let standard = UIToolbarAppearance()
standard.backgroundColor = UIColor.yellow
with the buttons. navigationController?.toolbar.scrollEdgeAppearance = standard

let button = UIBarButtonItem(image: UIImage(systemName: "trash"), primaryAction: Custom Navigation


UIAction(handler: { action in
print("Button pressed") 
}))
button.tintColor = UIColor.systemRed
self.setToolbarItems([button], animated: false) It is possible to hide the Navigation Bar and provide our own navigation
}
} tools. The option is available in the Attributes Inspector panel when the
 Navigation Controller is selected (see Figure 9-44). Clicking on the Shows
Navigation Bar option deactivates the bar and removes it from the scenes.
The code defines a UIToolbarAppearance object with a yellow background and Once the Navigation Bar is deactivated, it is our responsibility to provide
assigns it to the scrollEdgeAppearance property of the Navigation Controller's the tools the user needs to navigate.
toolbar property to modify the Toolbar's appearance. After this, we create a
bar button, as we did before for the Navigation Bar, and add it to the Figure 9-44: Interface without the Navigation Bar
Toolbar with the setToolbarItems() method provided by the view controller.
The result is shown below.

Figure 9-43: Custom Toolbar in portrait and landscape mode

Figure 9-44 introduces a simple interface. With the Navigation Bar


removed, we do not have a Back button anymore, so we added a button in
the second view called Close Second View to remove it. The view controller
for this scene has to include an Action for the button that calls the

popViewController() method of its Navigation Controller. This method removes
the current view and asks the Navigation Controller to show the previous
Do It Yourself: Update the ViewController class with the code in
view in the stack.
Listing 9-15 and run the application. Rotate the screen. You should
see something like Figure 9-43. You can also select the Navigation Listing 9-16: Defining our own method to navigate back
Controller, set the Shows toolbar option from the Attributes 
Inspector panel, and drag bar buttons to the Toolbar from the Library import UIKit
to see how the buttons are created in the Storyboard.
class SecondViewController: UIViewController {
@IBAction func closeView(_ sender: UIButton) {
navigationController?.popViewController(animated: true)
}
} Data Model

View controllers managed by a Navigation Controller serve a common


purpose and therefore often share common data. When working with
Navigation Controllers, we should not send information from one view
controller to another, as we did before with single view controllers, but
instead create a unique source of data. One of the objects frequently used
to store this data is the AppDelegate object. The advantage presented by this
object is that it is the delegate of the UIApplication object created to run the
application and therefore it is accessible from anywhere in the code.
As an example, we are going to recreate the application developed at the
beginning of this chapter using a Navigation Controller. The interface
includes two scenes embedded in a Navigation Controller, one with the
buttons to select the picture and another with an Image View and a Slider
to show the picture and rate it.

Figure 9-45: Picture application created with a Navigation Controller

The application works as before, the system shows the initial scene with
the menu first and then when the user clicks on any of the buttons it
transitions to the second scene to show the selected picture, but because

this time we use a Navigation Bar, we do not have to worry about the import UIKit
button required to go back to the previous scene. @main
class AppDelegate: UIResponder, UIApplicationDelegate {
var picturesList: [String]!
Do It Yourself: Create a new project. Embed the initial scene in a var ratings: [Int]!
Navigation Controller. Add a second scene to the Storyboard. var selectedPicture: Int!
Connect the initial scene to the second scene with a Show segue func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
(Figure 9-17). Assign the string "showPicture" to the segue's [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
picturesList = ["Husky", "Door"]
identifier (Figure 9-11). Create a new file with a view controller ratings = [0, 0]
called SecondViewController and assign it to the second scene (Figures 9- selectedPicture = 0
return true
5 and 9-6). Click on the Navigation Bars and assign the title "Menu" }
to the initial scene and "Picture" to the second scene (Figure 9-25). func application(_ application: UIApplication, configurationForConnecting
connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
Add buttons called Show Husky and Show Door to the initial scene UISceneConfiguration {
and assign the values 0 and 1 to their tag properties from the return UISceneConfiguration(name: "Default Configuration", sessionRole:
connectingSceneSession.role)
Attributes Inspector panel (this is to identify which button was }
pressed). Add an Image View, a label with the text "Rating", and a }

Slider to the second scene. Set a value of 0, a minimum of 0, and a
maximum of 5 for the Slider. Assign a higher vertical Content The picturesList property is an array with the names of the pictures available,
Hugging Priority and Content Compression Resistance Priority to the the ratings property is another array with the ratings assigned by the user to
Slider if necessary. Add the pictures husky.png and door.png to the each picture, and the selectedPicture property stores the index of the
Assets Catalog. The interface should look like Figure 9-45. currently selected picture. The first place where these values are required
is in the view controller of the initial scene. Here, we need to update the
The code is also like previous examples, but the picturesList, ratings, and selectedPicture property every time a button is pressed so that the rest of the
selectedPicture properties we used before to store the information are going application knows which picture was selected by the user.
to be defined in the application’s delegate, so every time the rating is
modified in the second scene, we do not need to send the data back to the Listing 9-18: Storing the index of the selected picture
initial view controller; all we need to do is access the app’s delegate and 
change the values of its properties. The following code illustrates how the import UIKit
properties should be declared and initialized in the delegate. class ViewController: UIViewController {
var mydelegate: AppDelegate!
Listing 9-17: Storing common data in the app’s delegate
override func viewDidLoad() {
 super.viewDidLoad()
let app = UIApplication.shared
mydelegate = app.delegate as? AppDelegate let picture = mydelegate.picturesList[selected]
} let rating = mydelegate.ratings[selected]
@IBAction func goToPicture(_ sender: UIButton) { sliderRating.value = Float(rating)
mydelegate.selectedPicture = sender.tag pictureView.image = UIImage(named: picture.lowercased())
performSegue(withIdentifier: "showPicture", sender: self) }
} @IBAction func changeRating(_ sender: UISlider) {
} let value = round(sender.value)
 sliderRating.value = value
let selected = mydelegate.selectedPicture ?? 0
mydelegate.ratings[selected] = Int(value)
The first thing we do in the ViewController class is to get a reference to the }
application’s delegate. We first get a reference to the UIApplication object }

created for the application and then read its delegate property. The property
we need to modify in the delegate is the selectedPicture property that stores
In this view controller, we read the three properties of the app’s delegate
the index of the selected picture. For this purpose, we have connected
to configure the view. First, we get the index of the selected picture and
both buttons, Show Husky and Show Door, to one Action called goToPicture().
use it to read the picture’s name and rating from the picturesList and ratings
When any of the buttons is pressed, we take the value of their tag property
arrays. This information is used next to update the Slider and the Image
and assign it to the selectedPicture property (the value 0 represents the husky
View before the interface is shown on the screen. The selectedPicture and
and the value 1 the door).
ratings properties are also used in the Action connected to the Slider to
At the end, we call the performSegue() method to transition to the second
update the value of the rating with the one selected by the user.
scene. Notice that this time we do not need to send any value to
SecondViewController because this view controller also takes the values from
Do It Yourself: Update the AppDelegate class in the
the app’s delegate, as shown next.
AppDelegate.swift file with the code in Listing 9-17. Connect the
Listing 9-19: Showing the selected picture and storing the rating in the Show Husky and Show Door buttons in the initial scene to an Action
app’s delegate called goToPicture(). Complete the ViewController class with the code in
 Listing 9-18. Connect the Slider and the Image View of the second
import UIKit scene to Outlets called sliderRating and pictureView, respectively. Create
an Action for the Slider called changeRating(). Complete the
class SecondViewController: UIViewController {
@IBOutlet weak var sliderRating: UISlider! SecondViewController class with the code in Listing 9-19. Run the
@IBOutlet weak var pictureView: UIImageView!
var mydelegate: AppDelegate!
application and modify the rating of the pictures to verify that the
values are preserved.
override func viewDidLoad() {
super.viewDidLoad()
let app = UIApplication.shared The use of the app’s delegate to store data is only recommended when the
mydelegate = app.delegate as? AppDelegate amount of data is not significant. For large amounts of data, it is better to
let selected = mydelegate.selectedPicture ?? 0 create global objects or structures that the application can access every

time necessary and where we can safely manage the information available. the Model is now the ApplicationData structure defined in Listing 9-20.
To do this in Swift is very easy. All the files are in the global space and are This model and the one created with the app's delegate before are
processed as soon as the application is launched. If we want to create a suitable for small to medium applications, but there are multiple
global object or structure that we can access from anywhere in our code, models and programming patterns available for professional
all we need to do is define it and initialize it in a new Swift file. applications. For instance, some applications implement singletons.
As any other files, Swift files are created from the File menu or pressing Singletons are defined by classes that make sure that one and only
Command + N on the keyboard, but instead of selecting the Cocoa Touch one object can be instantiated from the class. For more information,
Class option we click on the Swift File option (Figure 9-3). The name of the visit our website and follow the links for this chapter.
file in this case is just for reference, because except for a few comments
and import statements, no code is created for the template. Once the file is The structure declared in Listing 9-20 contains the same information as
added to the list of files in our application, we can define our custom before, but it provides a unique place to store and manage all the
classes or structures inside. For our example, we have created a file called information for our app. Now we can access this data from anywhere in
ApplicationData.swift and define a structure inside with the same name the code by just reading and writing the content of the AppData variable.
and all the properties necessary for our application. The following example shows how the ViewController of our application gets
the data from AppData.
Listing 9-20: Defining a global structure to store common data
 Listing 9-21: Accessing the data model from the ViewController class
import Foundation 
import UIKit
struct ApplicationData {
var picturesList: [String] class ViewController: UIViewController {
var ratings: [Int]
@IBAction func goToPicture(_ sender: UIButton) {
var selectedPicture: Int
AppData.selectedPicture = sender.tag
performSegue(withIdentifier: "showPicture", sender: self)
init() {
}
picturesList = ["Husky", "Door"]
}
ratings = [0, 0] 
selectedPicture = 0
}
} Since we do not have to get a reference to the app’s delegate anymore, all
var AppData = ApplicationData()

we need in the ViewController class is the Action for the buttons. When a
button is pressed, the goToPicture() method is executed as always, but this
IMPORTANT: The ApplicationData structure represents the model in time instead of accessing the selectedPicture property from the app’s delegate
our MVC, or Model-View-Controller (see Chapter 5). In our example, we do it from the AppData structure.
the Views (scenes) are created from the Storyboard, the Controllers The code for the SecondViewController can also be simplified. Getting the data
are the view controllers we associate with those views (scenes), and from a global structure makes everything easier.
9.3 Tab Bar Controllers
Listing 9-22: Accessing the data model from the SecondViewController class


import UIKit
A Tab Bar Controller is a simple container view controller that designates
class SecondViewController: UIViewController { two areas on the screen: one for the scenes and a smaller one at the
@IBOutlet weak var sliderRating: UISlider!
@IBOutlet weak var pictureView: UIImageView!
bottom for a bar with tabs that users can tap to select the scene they want
to see. Each tab is associated with only one scene and therefore we can
override func viewDidLoad() { use them to move from one scene to another.
super.viewDidLoad()
let selected = AppData.selectedPicture
let picture = AppData.picturesList[selected] Figure 9-46: Application based on a Tab Bar Controller
let rating = AppData.ratings[selected]
sliderRating.value = Float(rating)
pictureView.image = UIImage(named: picture.lowercased())
}
@IBAction func changeRating(_ sender: UISlider) {
let value = round(sender.value)
sliderRating.value = value
let selected = AppData.selectedPicture
AppData.ratings[selected] = Int(value)
}
}

Do It Yourself: Erase the properties in the AppDelegate class. Open


the File menu, click on New, and select the option File to create a
new Swift file. In the next window, click on the Swift File icon and
insert the name ApplicationData.swift. Open the new file and update 
the code with the model in Listing 9-20. Replace the ViewController
class with the class in Listing 9-21 and the SecondViewController class As with Navigation Controllers, there are two ways to add a Tab Bar
with the class in Listing 9-22. Run the application. It should work as Controller to the Storyboard. We can embed a scene inside a Tab Bar
before but now the data is taken from the AppData structure. Controller from the Editor menu (Editor/Embed In/Tab Bar Controller) or
drag the Tab Bar Controller option from the Library. Figure 9-47, below,
shows what we see when we embed a single scene in a Tab Bar Controller.

Figure 9-47: Initial scene embedded in a Tab Bar Controller

and generates the bar (Figure 9-48, left). If there is not enough space on
the bar to place all the tabs available, the controller adds a tab called More
that the user can tap to select the tabs that are not visible.

The scenes managed by the Tab Bar Controller are connected with a segue
called View Controllers. If we want to incorporate another scene to the Tab
Bar Controller, we must add it to the Storyboard and create a View
Controllers segue from the Tab Bar Controller to the new scene.

Figure 9-48: Multiple scenes managed by the Tab Bar Controller

We can add all the scenes we want. When a scene is added to the Tab Bar
Controller, the system automatically includes the corresponding tab at the
bottom of the scene. The Tab Bar Controller gets the tabs from the scenes
Tabs These options define the image and the name. If we want to provide our
own values, we must set the System Item option to Custom and specify the

information below. We can provide our own images or specify an SF
Symbol. Custom images must be provided with a size of 30 pixels by 30
The bar is managed by the Tab Bar Controller, but the tabs are provided by
pixels (60x60 for the 2x scale and 90x90 for the 3x scale) and a transparent
the view controllers to which they belong. To change the values and
background. For our example, we have created the images
appearance of the tabs, we can click on them and edit their values from
iconweather.png for the initial scene and iconsettings.png for the second
the Attributes Inspector panel.
scene. Figure 9-51, below, shows what the bar looks like when we select
these images and insert the titles "Weather" and "Settings".
Figure 9-49: Options to configure the tab
Figure 9-51: Custom tabs

 Do It Yourself: Create a project and embed the initial scene in a


Tab Bar Controller (Editor/Embed In/Tab Bar Controller). Add a
Each tab has a title, image, selected image, and a landscape image to second scene and create a segue of type View Controllers from the
identify the scenes they represent. There are two alternatives to configure Tab Bar Controller to the new scene. Add labels to identify each
the tab: we can provide a custom name and images, or we can select a tab scene, as shown in Figure 9-48. Create a view controller called
predefined by the system from the option called System Item. If we click on
SecondViewController and assign it to the second scene. Download from
this field, a popup window shows a list of all the tabs available. Figure 9-50
our website the images iconweather.png and iconsettings.png and
shows what the bottom of the scenes look like after the style Favorites and
add them to the Assets Catalog. Click on the initial scene’s tab, go to
Search were selected for the tabs.
the Bar Item section in the Attributes Inspector panel, and insert the
Figure 9-50: Tabs defined by the system name "Weather" and the image iconweather.png. Repeat the same
with the name "Settings" and the image iconsettings.png for the
second scene. The bars should look like Figure 9-51. Run the
application and tap on the items to select a scene.

Tabs are created from the UITabBarItem class. This is a subclass of the
UIBarItem class, which is also used to create the buttons for Navigation

Controllers. In addition to the properties defined by the UIBarItem class to the system. Figure 9-52 shows what we see when we run the app for the
configure the items, like title, image, and landscapeImagePhone, the UITabBarItem first time and after the button is pressed 12 times.
class includes the following.
Figure 9-52: Tab with a badge and a new title
badgeValue—This property sets or returns the value of the tab’s
badge (shown inside a circle at the top-right corner of the tab's icon).
It is an optional of type String.

The tabs are configured from the view controllers to which they belong.

The UIViewController class includes the tabBarItem property to access the view
controller’s tab. The next example adds an Action for a button to update
Do It Yourself: Add a button to the initial scene of the interface in
the badge when the button is pressed.
Figure 9-48. Connect the button to an Action called updateBadge().
Complete the ViewController class with the code in Listing 9-23. Run
Listing 9-23: Updating the badge from the view controller

the application and press the button. You should see something like
import UIKit
Figure 9-52.

class ViewController: UIViewController {


var counter = 1

@IBAction func updateBadge(_ sender: UIButton) {


if let item = self.tabBarItem {
item.title = "New Weather"
item.badgeValue = String(counter)
counter += 1
}
}
}

This code gets a reference to the view's tab from the tabBarItem property,
updates the tab's title with the string "New Weather" and the badgeValue
property with the value of the counter property. The counter is incremented
every time the button is pressed to illustrate how the badge is modified by
Tab Bar Controller items in the standard configuration.
 compactInlineLayoutAppearance—This property sets or returns a
object that provides the appearance of the
UITabBarItemAppearance
View controllers manage their own tabs, but the bar and the list of view items in the compact configuration.
controllers available is managed by the Tab Bar Controller. The inlineLayoutAppearance—This property sets or returns a
UITabBarController class includes the following properties to store the view
UITabBarItemAppearance object that provides the appearance of the
controllers, keep a reference to the selected one, and access the bar.
items in the inline configuration.

tabBar—This property returns a reference to the UITabBar object that stackedItemPositioning—This property sets or returns a value that
represents the bar. determines the position of the buttons. It is an enumeration of type
ItemPositioning with the values fill, centered, and automatic.
viewControllers—This property sets or returns the view controllers
managed by the Tab Bar Controller. It is an array of UIViewController stackedItemSpacing—This property sets or returns a CGFloat value
objects. that determines the space between items.

selectedViewController—This property returns a reference to the stackedItemWidth—This property sets or returns a CGFloat value
view controller of the scene currently shown to the user. that determines the width of the items.

selectedIndex—This property returns the index of the view The UITabBarAppearance class defines the appearance of the bar. If we want
controller that is currently being shown to the user. It is a value of to modify the appearance of the items, we must assign an appearance
type Int. object to the stackedLayoutAppearance, compactInlineLayoutAppearance, or
inlineLayoutAppearance properties. These properties take an object of type
The bar is created from the UITabBar class, which includes the UITabBarItemAppearance, which in turn includes the normal, selected, disabled, and
standardAppearance and scrollEdgeAppearance properties to modify the bar's focused properties to configure the appearance of the items in every state.
appearance. These properties take an object of type UITabBarAppearance. The values are defined by the UITabBarItemStateAppearance class, which
This is a subclass of UIBarAppearance, which is also available for Navigation includes the following properties for configuration.
Bars and Toolbars. Therefore, a UITabBarAppearance object includes
properties we used before like backgroundEffect, backgroundColor, and titleTextAttributes—This property sets or returns the attributes of
backgroundImage, but also its own properties. The following are the most
the item's title. The value is a dictionary with NSAttributedString keys.
frequently used.
titlePositionAdjustment—This property sets or returns the
stackedLayoutAppearance—This property sets or returns a position of the item's title. It is an object of type UIOffset, which
UITabBarItemAppearance object that provides the appearance of the includes the horizontal and vertical properties to set the horizontal and
vertical offsets.

iconColor—This property sets or returns the color of the item's standard.inlineLayoutAppearance.normal.titleTextAttributes = attr
}
image. standard.stackedLayoutAppearance.selected.iconColor = .systemGreen
standard.compactInlineLayoutAppearance.selected.iconColor = .systemGreen
badgeTextAttributes—This property sets or returns the attributes standard.inlineLayoutAppearance.selected.iconColor = .systemGreen
of the text displayed by the badge. The value is a dictionary with if let attr = try? Dictionary(attributesSelected, including: \.uiKit) {
standard.stackedLayoutAppearance.selected.titleTextAttributes = attr
NSAttributedString keys. standard.compactInlineLayoutAppearance.selected.titleTextAttributes = attr
standard.inlineLayoutAppearance.selected.titleTextAttributes = attr
badgeBackgroundColor—This property sets or returns the badge's }
background color. self.tabBar.standardAppearance = standard
}
}
The UIViewController class includes the tabBarController property to access the 
Tab Bar Controller to which the scene belongs, but because there is usually
only one Tab Bar Controller per application it is better to create a subclass A Tab Bar can adopt three different configurations. The standard
of UITabBarController and manage the view controllers and the bar from it. configuration is when the bar is shown with a standard height and the text
For our example, we have created a file called MyTabViewController.swift below the image. The appearance for this configuration is defined by the
with a subclass of the UITabBarController called MyTabViewController and assign stackedLayoutAppearance property (the elements of the tab are stacked).
it to the Tab Bar Controller of the interface in Figure 9-48. Another possible configuration is compact. In this configuration, the height
of the Tab Bar is reduced to make room for the content. This appearance is
Listing 9-24: Modifying the appearance of the bar defined by the compactInlineLayoutAppearance property. Finally, there is a
 configuration in which the elements of the tab are shown side by side (the
import UIKit image on the left and the text on the right). This appearance is defined by
the inlineLayoutAppearance property. The configuration is determined by the
class MyTabViewController: UITabBarController {
override func viewDidLoad() { system according to the space available, but we can assign the same
super.viewDidLoad() appearance to all the three properties to make sure that the Tab Bar
var attributesNormal = AttributeContainer()
attributesNormal.foregroundColor = .systemGray
always looks the same, as we did in this example.
The process to configure the bar is like those implemented before to
var attributesSelected = AttributeContainer() configure the Navigation Bar and the Toolbar. In this case, we start by
attributesSelected.foregroundColor = .systemGreen
creating two AttributeContainer structures to define the attributes for the text,
let standard = UITabBarAppearance() then we create the UITabBarAppearance object and proceed to define the
standard.backgroundColor = .systemGray5
standard.stackedLayoutAppearance.normal.iconColor = .systemGray
appearances for every configuration (standard, compact, and inline).
standard.compactInlineLayoutAppearance.normal.iconColor = .systemGray Finally, we access the Tab Bar with the tabBar property provided by the
standard.inlineLayoutAppearance.normal.iconColor = .systemGray UITabBarController object and assign the appearance object to the
if let attr = try? Dictionary(attributesNormal, including: \.uiKit) {
standard.stackedLayoutAppearance.normal.titleTextAttributes = attr standardAppearance property of the bar to modify its appearance.
standard.compactInlineLayoutAppearance.normal.titleTextAttributes = attr
Do It Yourself: Create a new file with a subclass of the
class called MyTabViewController and assign it to the
UITabBarController
Tab Bar Controller introduced in Figure 9-48. Complete the subclass
with the code in Listing 9-24. The application now shows the items in
gray, but they change to green when selected.

By default, the items on the bar are expanded to fill the space available,
but we can center the items and then assign a custom width and space in
between with the rest of the properties provided by the UITabBarAppearance
class. 

Listing 9-25: Configuring the items There are more tasks we can perform from the UITabBarController subclass
 other than configuring the bar. Important things like establishing which
import UIKit scene will be shown first or setting the badges for each tab can be done
from this subclass. The following example modifies the selectedIndex property
class MyTabViewController: UITabBarController {
override func viewDidLoad() { to declare the second scene as the initial scene.
super.viewDidLoad()
let standard = UITabBarAppearance()
standard.stackedItemPositioning = .centered Listing 9-26: Initializing the Tab Bar Controller
standard.stackedItemWidth = 50 
standard.stackedItemSpacing = 50
import UIKit
self.tabBar.standardAppearance = standard
}
class MyTabViewController: UITabBarController {
}
override func viewDidLoad() {

super.viewDidLoad()
let list = viewControllers!
The stackedItemPositioning property can take two values, fill and centered. The let controller = list[0] as! ViewController
controller.tabBarItem?.badgeValue = String(20)
value by default is fill, which causes the items to expand proportionally to
fill the available space. This means that the size and space in between self.selectedIndex = 1
items is determined by the system. But if the value centered is assigned to }
}
this property, as we did in Listing 9-25, we can assign a custom size and 
space in between. In our example, we give the items a width and a space
of 50 points. The view controllers managed by the Tab Bar Controller are stored in an
array called viewControllers in the order they were connected in the
Figure 9-53: Items with custom size Storyboard. In our example, the view controller for the Weather scene was

stored at index 0 and the view controller for the Settings scene was stored Tab Bar Controller Delegate
at index 1. Therefore, to add a badge to the first scene, we get the first

element of the viewControllers array, cast it as ViewController (the array
contains elements of type UIViewController), and access its properties. On the
Tab Bar Controllers can use a delegate object to report when something
other hand, the Settings view controller is at index 1, so we assign the
happened or is about to happen with the tabs. The UIKit framework
value 1 to the selectedIndex property to declare this as the initial scene.
includes the UITabBarControllerDelegate protocol for this purpose. The
following are some of the methods defined by the protocol.

tabBarController(UITabBarController, didSelect:
UIViewController)—This method is called by the UITabBarController
object when a tab is selected. The didSelect argument is a reference
to the view controller of the selected tab.
tabBarController(UITabBarController, shouldSelect:
UIViewController)—This method is called by the UITabBarController
object to know whether it should let the user select a tab. The
method returns a Boolean value that communicates the decision
made by the app. The shouldSelect argument is a reference to the
view controller of the selected tab.

The delegate object may be an external object or the UITabBarController


subclass that is controlling our Tab Bar Controller. The following example
defines our MyTabViewController class as its own delegate to perform a task
when a tab is selected.

Listing 9-27: Defining the Tab Bar Controller’s delegate



import UIKit

class MyTabViewController: UITabBarController, UITabBarControllerDelegate {


override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: super.viewDidLoad()
UIViewController) { delegate = self
let list = viewControllers! }
let controller = list[1] func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController:
if viewController === controller { UIViewController) -> Bool {
print("It's Settings") if let controller = viewController as? SecondViewController {
} let control = controller.myproperty
} if control != 0 {
} return false
 }
}
return true
To turn the MyTabViewController class of our example into its own }
delegate, we must declare that the class conforms to the }

UITabBarControllerDelegate protocol first, and then assign self to its delegate
property. From that moment on, the MyTabViewController object will call the
The protocol method implemented in this example is called when a
delegate’s methods on itself. In Listing 9-27, we declare the method that is
tab is selected but before the scene is shown on the screen. From this
called when the user selects a tab. To check which tab was selected, we
method, we must return a Boolean value to indicate whether the action is
implement the identity operator (===) to compare the value on the
valid or not. To know what value to return, the code in Listing 9-28 detects
viewController argument with the reference stored at index 1 in the
the scene the user is trying to open by casting the value in the viewController
viewControllers array. This reference corresponds to the SecondViewController
argument to the class corresponding to the view controller of the Settings
object and therefore the task is performed when the Settings view is show
scene (SecondViewController). In case of success, we store the result in the
on the screen (the === operator compares objects' references).
controller constant and use this reference to access a property called
There are other ways to detect the type of the selected view
myproperty. If the value of this property is different from 0, the code returns
controller. For example, we could try to convert it to one of our
false and does not let the user select the tab.
UIViewController subclasses with the as? operator and compare the value
returned by the conversion to nil. If the view controller received by the
Do It Yourself: Copy the example you want to try in the
method is of type SecondViewController, the condition will be true, and we can
MyTabViewController.swift file created before and run the
perform the task as we did in Listing 9-27. Following this procedure, we
application. The code in Listing 9-28 assumes that you have declared
could also read the properties of this object, as shown next.
a property called myproperty in the SecondViewController class assigned to
the second scene and initialize it with an integer. If the value is
Listing 9-28: Allowing the selection of a tab

different from 0, the delegate method returns false and therefore you
import UIKit will not be able to select the Settings tab.

class MyTabViewController: UITabBarController, UITabBarControllerDelegate {


override func viewDidLoad() {

CHAPTER 10 - TABLE VIEWS


10.1 Tables that provide the configuration and the data, but since iOS 14, the
data is provided by Diffable Data Sources and the cells are defined by

configuration objects. This is how Table Views work in iOS 15 and
therefore it is the approach we take in this book. To learn more
Computers are experts at organizing information in lists of values, and
about the old Table View delegates, visit our website and follow the
therefore it didn't take long for computer systems to adopt the concepts of
links for this chapter.
tables to present this type of information. A table is an organizational
system that proposes an arrangement of data in rows and columns. Apple
has always provided the tools to create tables on its systems, but the small
screens of mobile devices forced the company to introduce a customized
version of tables composed of only one column.
Despite their simplicity, tables in iOS are very powerful. They provide tools
to manage the data, organize it in single lists or sections, and are built
inside a Scroll View, allowing the content to be shown with no limitations
whatsoever. Figure 10-1 shows the three possible configurations (Plain,
Grouped, and Inset Grouped).

Figure 10-1: Table Views styles

iOS tables can present the information on a list, like the table on the left of
Figure 10-1, or organized in groups, like the ones at the center and right of
Figure 10-1, and they can also display images and text, as in this example,
only text, or be completely customized, as we will see later.

IMPORTANT: There are at least two ways to create and configure


Table Views. Traditionally, Table Views work with delegate objects

Table Views estimatedRowHeight—This property sets or returns a CGFloat value


that determines the approximate size of the cell. It is intended to

improve performance.
Tables are created from the UITableView class. The following are the separatorStyle—This property sets or returns the style for the cells’
initializer provided by the class to create a table from code and the option separators. It is an enumeration called SeparatorStyle with the values
in the Library to add a table to a scene. none and singleLine.

separatorColor—This property sets or returns the color for the cells’


UITableView(frame: CGRect, style: Style)—This initializer creates
separators. It is an optional of type UIColor.
a UITableView object with the frame specified by the frame argument.
The style argument is an enumeration called Style with the values plain,
separatorInset—This property sets or returns the padding for the
cells’ separators. It is a structure of type UIEdgeInsets with the
grouped, and insetGrouped.
properties top, left, bottom, and right (only the left and right values are
Figure 10-2: Table View option in the Library considered).
indexPathForSelectedRow—This property returns an IndexPath
structure with the location of the currently selected cell.

 selectRow(at: IndexPath?, animated: Bool, scrollPosition:


ScrollPosition)—This method selects the cell at the location
The UITableView class includes multiple properties and methods to configure specified by the at argument. The animated argument indicates
the table. The following are the most frequently used. whether the process is going to be animated or not, and the
scrollPosition argument indicates if the table will scroll to the position
allowsSelection—This property sets or returns a Boolean value that of the cell and how. It is an enumeration called ScrollPosition with the
determines if the user is allowed to select a cell or not. values none, top, middle, and bottom.
allowsMultipleSelection—This property sets or returns a Boolean deselectRow(at: IndexPath, animated: Bool)—This method
value that determines if the user is allowed to select multiple cells or deselects the cell at the location specified by the at argument. The
not. animated argument indicates whether the process is going to be
isEditing—This property sets or returns a Boolean value that animated or not.
indicates whether the table is in edition mode or not. The edition scrollToRow(at: IndexPath, at: ScrollPosition, animated: Bool)
mode is used to delete or move rows. —This method scrolls the Table View to show on the screen the cell at
rowHeight—This property sets or returns a CGFloat value that the position indicated by the at argument. The position argument
determines the cells’ height. indicates if the table scrolls to the position of the cell and how. It is an
enumeration called ScrollPosition with the values none, top, middle, and Table View Cells
bottom. The animated argument indicates whether the process is going

to be animated.
setEditing(Bool, animated: Bool)—This method sets the value of Tables present the information in cells, one per row, with each cell in
the isEditing property. The first argument is the value we want to assign charge of displaying a unique piece of data. Cells are created from the
to the property, and the animated argument determines if the change UITableViewCell class, but we do not instantiate each cell for the table, we

will be animated. define a prototype cell and then ask the system to create the cells from this
prototype. This is so the system can keep in memory only the cells required
at any given moment and improve performance. The Library includes the
following option to add a prototype cell to the table.

Figure 10-3: Table View Cell option in the Library

If we want to include a prototype cell from code (a process called


registration), we can use the following method.

register(AnyClass?, forCellReuseIdentifier: String)—This


method registers a prototype cell. The fist argument is the class that is
going to be used to create the cells (the UITableViewCell class or a
subclass of it), and the forCellReuseIdentifier argument specifies the
string the table is going to use to identify this prototype cell.

Once we have a prototype cell, we can use it to display the information on


the table. The following is the method we need to implement to get a
reference to the cell for a row.

dequeueReusableCell(withIdentifier: String, for: IndexPath)—


This method returns a reusable cell with the identifier specified by the

withIdentifier argument. The for argument is an IndexPath structure


that determines the cell's location. The attributedText and secondaryAttributedText properties allow us to declare the
cell's main and secondary texts with attributed strings, but we can also
This method gets the cell for a row, but the content of that cell is defined specify the text and secondary text with a string and assign common
by a configuration object. This is an object created from the attributes. The UIListContentConfiguration structure includes the following
UIListContentConfiguration structure. The UITableViewCell class includes the properties to assign attributes to the texts and the image.
following method to create a UIListContentConfiguration structure with a
configuration by default. textProperties—This property sets or returns a TextProperties
structure with the attributes we want to assign to the main text.
defaultContentConfiguration()—This method returns a secondaryTextProperties—This property sets or returns a
UIListContentConfiguration structure with a default configuration. structure with the attributes we want to assign to the
TextProperties
secondary text.
The UIListContentConfiguration structure also includes type methods that
imageProperties—This property sets or returns an ImageProperties
return standard configurations for cells, including cell(), subtitleCell(),
valueCell(), sidebarCell(), sidebarSubtitleCell(), accompaniedSidebarCell(), and
structure with the attributes we want to assign to the image.
accompaniedSidebarSubtitleCell().
After we get the configuration, we must assign the values we want to The attributes for the text are defined by a TextProperties structure. For this
display inside the cell. A standard cell can display three values by default: a purpose, the structure includes the following properties.
text, a secondary text, and an image. The UIListContentConfiguration structure
includes the following properties to specify these values. font—This property sets or returns the font. It is a value of type
UIFont.

text—This property sets or returns the cell's main text. It is an color—This property sets or returns the color. It is a value of type
optional of type String. UIColor.

attributedText—This property sets or returns the cell's attributed alignment—This property sets or returns the alignment. It is a
text. It is an optional of type NSAttributedString. TextAlignment enumeration with the values center, justified, and natural.
secondaryText—This property sets or returns the cell's secondary lineBreakMode—This property sets or returns the mode used to
text. It is an optional of type String. truncate the text. It is an NSLineBreakMode enumeration with the values
secondaryAttributedText—This property sets or returns the cell's byWordWrapping, byCharWrapping, byClipping, byTruncatingHead,

attributed secondary text. It is an optional of type NSAttributedString. byTruncatingTail, and byTruncatingMiddle.

image—This property sets or returns the cell's image. It is an optional numberOfLines—This property sets or returns the maximum lines
of type UIImage. of text allowed. It is a value of type Int. The value 0 indicates unlimited
lines.
accessoryType—This property sets or returns the type of the
On the other hand, the attributes for the image are defined by an accessory view. It is an enumeration called AccessoryType with the values
ImageProperties structure. For this purpose, the structure includes the none, disclosureIndicator, detailDisclosureButton, checkmark, and detailButton.
following properties.

cornerRadius—This property sets or returns a CGFloat value that


determines the image's corner radius.
maximumSize—This property sets or returns a CGSize value with the
image's width and height. The image is resized to meet these values.
reservedLayoutSize—This property sets or returns a CGSize value
that determines the space reserved for the images (used to align the
images with the rest of the content).

After the configuration object is defined, we must assign it to the cell. The
UITableViewCell class includes the following properties for this purpose.

contentConfiguration—This property sets or returns the


UIListContentConfiguration structure that determines the cell's
configuration.
automaticallyUpdatesContentConfiguration—This property sets
or returns a Boolean value that indicates whether the configuration is
going to be updated when the state of the cell changes. The value by
default is true.

In addition to the text, the secondary text, and the image, a cell can also
include a view on the side. These views, called accessories, are used to
reflect or propose user interaction. For instance, there is an accessory
called Disclosure Indicator used to indicate to the user that there is more
information available for the item. Accessories are defined by the cell. The
UITableViewCell includes the following property for this purpose.

Data Source
The UITableViewDiffableDataSource object generates the cells from a snapshot

of the data created by a NSDiffableDataSourceSnapshot object. The following are
some of the properties and methods defined by the
The data is provided to the Table View by two objects: an object of the
NSDiffableDataSourceSnapshot class to process the values.
UITableViewDiffableDataSource class, which takes care of defining the cells, and
an object of the NSDiffableDataSourceSnapshot class, responsible for keeping
numberOfItems—This property returns the number of values
the Table View up to date. The UITableViewDiffableDataSource class provides the
managed by the snapshot. There is also the numberOfSections property
following initializer and methods to generate the cells and apply the
changes. to return the number of sections.
itemIdentifiers—This property returns an array with the identifiers
UITableViewDiffableDataSource(tableView: UITableView, of the values managed by the snapshot. There is also the
cellProvider: Closure)—This initializer creates a data source for the sectionIdentifiers property for the sections.
Table View specified by the tableView argument. The cellProvider numberOfItems(inSection: SectionIdentifierType)—This
argument is a closure that is called every time a cell is required. The method returns the number of items in the section specified by the
closure receives three values: a reference to the Table View, the cell's inSection argument.
index path, and a reference to the data that must be shown by the
appendItems([ItemIdentifierType], toSection:
cell.
SectionIdentifierType?)—This method adds to the snapshot the
snapshot()—This method returns a reference to the current items specified by the first argument in the section specified by the
snapshot used by the data source to get the data. toSection argument. If there is only one section, the second argument
apply(NSDiffableDataSourceSnapshot, animatingDifferences: may be ignored.
Bool, completion: Closure)—This method applies the snapshot appendSections([SectionIdentifierType])—This method adds to
provided by the first argument to the data source object. It is used to the snapshot the sections specified by the argument.
update the Table View with new values. The animatingDifferences
insertItems([ItemIdentifierType], afterItem:
argument determines if the changes on the table will be animated,
ItemIdentifierType)—This method inserts the items specified by the
and the completion argument is a closure to be executed when the
first argument after the item specified by the afterItem argument.
process is over. These last two arguments may be ignored.
insertItems([ItemIdentifierType], beforeItem:
defaultRowAnimation—This property sets or returns a value that ItemIdentifierType)—This method inserts the items specified by the
determines how the insertion or deletion of cells will be animated. It
first argument before the item specified by the beforeItem argument.
is an enumeration of type RowAnimation with the values fade, right, left,
top, bottom, middle, automatic, and none.
insertSections([SectionIdentifierType], afterSection: moveSection(SectionIdentifierType, beforeSection:
SectionIdentifierType)—This method inserts the sections specified SectionIdentifierType)—This method moves the section specified
by the first argument after the section specified by the afterSection by the first argument to the position before the section specified by
argument. the beforeSection argument.
insertSections([SectionIdentifierType], beforeSection: reloadItems([ItemIdentifierType])—This method reloads the
SectionIdentifierType)—This method inserts the sections specified items specified by the argument. It updates the items with the current
by the first argument before the section specified by the values from the model.
beforeSection argument. reloadSections([SectionIdentifierType])—This method reloads
deleteItems([ItemIdentifierType])—This method removes from the sections specified by the argument. It updates the sections with
the snapshot the items specified by the argument. the current values from the model.
deleteSections([SectionIdentifierType])—This method removes
from the snapshot the sections specified by the argument.
reconfigureItems([ItemIdentifierType])—This method updates
the values for the items specified by the argument.
deleteAllItems()—This method removes all the items from the
snapshot.
moveItem(ItemIdentifierType, afterItem: ItemIdentifierType)
—This method moves the item specified by the first argument to the
position after the item specified by the afterItem argument.
moveItem(ItemIdentifierType, beforeItem:
ItemIdentifierType)—This method moves the item specified by the
first argument to the position before the item specified by the
beforeItem argument.
moveSection(SectionIdentifierType, afterSection:
SectionIdentifierType)—This method moves the section specified
by the first argument to the position after the section specified by the
afterSection argument.

Index Paths and Identifiers


indexPath(for: ItemIdentifierType)—This method returns an

structure with the location of the cell identified by the value
IndexPath

Cells and sections are identified by their position in the Table View. provided by the argument.
Sections are assigned a consecutive index starting from 0, and then the itemIdentifier(for: IndexPath)—This method returns the identifier
cells inside each section are also identified with a consecutive index of the cell at the index path specified by the argument.
starting from 0. Therefore, to identify a cell, we need the index of the cell
and the index of the section the cell belongs to. Table Views store this
information in a structure of type IndexPath. The values are returned by the
following properties.

section—This property returns the index of the section where the


cell is located.
row—This property returns the index of the cell.
item—This property returns the index of the item in a Collection
View.

IndexPathstructures are automatically defined by the Table View, but there


are situations in which we must create them ourselves. The structure
includes the following initializer.

IndexPath(item: Int, section: Int)—This initializer creates an


IndexPath structure with the values provided by the arguments.

As we already mentioned, Table Views get the data to display from a


diffable data source. Diffable data sources work with their own identifiers;
they use the identification values provided by the data. Because Table
Views still need to call their delegate methods to perform most of the
configuration tasks, there are times when we need to convert IndexPath
values into identifiers and vice versa. The UITableViewDiffableDataSource class
includes the following methods for this purpose.
Implementing Table Views have seen how to create a model in Chapter 9 (see Listing 9-20), but
diffable data sources and snapshots have a few requirements. Diffable data

sources do not work with the data itself, they store the data identifiers.
These identifiers must conform to the Hashable protocol, so the values can
Table Views are normal views and therefore they are positioned and sized
be differentiated. Swift standard data types, such as strings, integers, and
inside the main view with constraints. Although we can give them any size
enumerations, conform to this protocol by default, but if we want to use
we want, due to the type of information they manage, they are usually
custom data types to store the data, they must conform to the Identifiable
defined of the size of the screen.
protocol and define the id property required by the protocol with a Hashable
value.
Figure 10-4: Table View pinned to the Safe Area
Table Views require at least one section to show the data, so we also need
identifiers for the sections. In the following model, we provide an
enumeration with a single value to represent the only section we need for
now, and a class with a Hashable identifier to store the data.

Listing 10-1: Defining a model for a diffable data source



import UIKit

enum Sections {
case main
}
class ItemsData: Identifiable {
var id: UUID = UUID()
var name: String
var image: String
var calories: Int
var selected: Bool

init(_ name: String, _ image: String, _ calories: Int, _ selected: Bool) {


self.name = name
self.image = image
self.calories = calories
 self.selected = selected
}
}
To provide the data for the table, we need three elements: the model struct ApplicationData {
var dataSource: UITableViewDiffableDataSource<Sections, ItemsData.ID>!
where the data is stored, a snapshot to keep the table up to date with the
changes in the model, and a diffable data source to configure the cells. We var items: [ItemsData] = [] {
didSet {

items.sort(by: { $0.name < $1.name }) defines a typealias for the property's data type called ID (a typealias is an
}
} alternative name for a data type). By using this typealias we make sure that
init() { the data type assigned to the diffable data source is the same assigned to
items.append(ItemsData("Bagels", "bagels", 250, false))
items.append(ItemsData("Brownies", "brownies", 466, false))
the id property. This makes the code more readable and easy to update.
items.append(ItemsData("Butter", "butter", 717, false)) The items property includes the didSet method to sort the values
items.append(ItemsData("Cheese", "cheese", 402, false)) alphabetically every time changes are introduced by the user (the values
items.append(ItemsData("Coffee", "coffee", 0, false))
items.append(ItemsData("Cookies", "cookies", 502, false)) are modified, deleted, or new values are added).
items.append(ItemsData("Donuts", "donuts", 452, false)) With the model ready, now the view controller has all the information it
items.append(ItemsData("Granola", "granola", 471, false))
items.append(ItemsData("Juice", "juice", 23, false))
needs to configure the table and present the data on the screen.
items.append(ItemsData("Lemonade", "lemonade", 40, false))
items.append(ItemsData("Lettuce", "lettuce", 15, false)) Listing 10-2: Defining the diffable data source and the snapshot
items.append(ItemsData("Milk", "milk", 42, false))
items.append(ItemsData("Oatmeal", "oatmeal", 68, false)) 
items.append(ItemsData("Potatoes", "potato", 77, false)) import UIKit
items.append(ItemsData("Tomatoes", "tomato", 18, false))
items.append(ItemsData("Yogurt", "yogurt", 59, false)) class ViewController: UIViewController {
} @IBOutlet weak var myTable: UITableView!
}
var AppData = ApplicationData() override func viewDidLoad() {
 super.viewDidLoad()
myTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
The ItemsData class includes four properties for the values (name, image,
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>
calories, and selected), and the id property defined with a UUID value. This is a (tableView: myTable) { tableView, indexPath, itemID in
structure that always produces a unique value and that's what we are let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
going to use to identify each item on the table. if let item = AppData.items.first(where: { $0.id == itemID }) {
Next is the definition of the structure to contain the model. This is the var config = cell.defaultContentConfiguration()
config.text = item.name
same ApplicationData structure we used before for the examples in Chapter 9.
cell.contentConfiguration = config
The structure includes a property called dataSource to store the diffable data }
source, a property called items to store the values for each cell, and an return cell
}
initializer to provide initial values to work with. var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
Notice that the UITableViewDiffableDataSource class is generic, and therefore it snapshot.appendSections([.main])
snapshot.appendItems(AppData.items.map({ $0.id }))
must be declared with the data types of the values is going to store (the AppData.dataSource.apply(snapshot)
data types used to identify the sections and the items). For the sections it }
is easy, the identifier is a value of type Sections, but the data type of the }

items' identifiers depends on the data type of the id property. In this case,
we could have specified the UUID data type, but the Identifier protocol
A Table View needs a prototype cell. This is like a template the table uses and requires us to specify the data types of the identifiers we are going to
to create all the cells it needs to display the data. Although prototype cells work with. In this case, Sections for the sections and ItemsData.ID for the data.
can be added to the Table View in the Storyboard, Apple's technologies are After the object is created, we must add the sections with the
leaning towards the optimizations of elements created from code, so in appendSections() method and all the items IDs we want to show to the user
this example we have decided to follow that approach. Using the register() with the appendItems() method. Notice that because the items array contains a
method, we define a prototype cell with the myCell identifier and then to list of items, we map the array with the map() method to get an array of
create the diffable data source with this cell. identifiers instead. Finally, the snapshot is applied to the diffable data
The UITableViewDiffableDataSource initializer takes a reference to the Table source with the apply() method and the values appear on the screen.
View and a closure that is called every time the table needs a cell (defined
as a trailing closure in this example). The closure receives three values we Figure 10-5: Table View with standard configuration
must use to create and configure the cell: a reference to the Table View,
the index path with the location of the cell, and the UUID value that
identifies the item to be shown in that row. The cell is created by the
dequeueReusableCell() method. This method creates a new UITableViewCell
object from a prototype cell or returns an existent object from a cell that is
not currently in use. Whatever the origin, we always must update the
configuration with the values of the current item, and the first step is to get
the item. For this purpose, we call the first(where:) method on the items array.
This method compares the value of the id property of each item with the ID
received by the closure and returns the item which ID matches. With this
value, we can finally configure the cell.
The configuration is defined by a UIListContentConfiguration structure. In this
example, we use the instance returned by the defaultContentConfiguration()
method. This is a standard configuration that includes the image on the
left, the text on top, and a secondary text with a smaller font at the 
bottom. Although the configuration structure provides styles for every
element in the cell, we can define only those we need. In this case, we only Do It Yourself: Create a new project. Add a Table View to the scene
want the cell to show the name of the item, so we assign that value to the and pin it to the Safe Area. Connect the table to an Outlet in the
text property. After the configuration structure is ready, we assign it to the view controller called myTable. Create a Swift file called
contentConfiguration property to apply it to the cell.
ApplicationData.swift to store the code in Listing 10-1. Complete the
The Table View shows the cells on the screen, the diffable data source
ViewController class with the code in Listing 10-2. Run the application.
configures the cells with the data, but the data is managed by a snapshot
You should see a Table View like in Figure 10-5.
created from a NSDiffableDataSourceSnapshot object. This class is also generic

var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()


snapshot.appendSections([.main])
IMPORTANT: By default, the apply() method tells the data source to snapshot.appendItems(AppData.items.map({ $0.id }))
animate the changes, but you can modify that behavior by specifying AppData.dataSource.apply(snapshot)
}
the animatingDifferences argument with the value false. }

We can take advantage of the rest of the elements in a standard cell to
present more information. The following example configures the cell with a This diffable data source includes the text, as before, but also the
text, a secondary text, and an image. Notice that because the code for the secondary text and the image. Notice that the images we used are too big
for the table, so we specify the maximum size with a CGSize value. The
diffable data source has grown, we have moved it along with the snapshot
images are reduced, and the cell's layout adapts to the elements available.
to two custom methods (prepareDataSource() and prepareSnapshot()).
Figure 10-6: Cells with all the standard elements
Listing 10-3: Including more information in the cell

import UIKit

class ViewController: UIViewController {


@IBOutlet weak var myTable: UITableView!

override func viewDidLoad() {


super.viewDidLoad()
myTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
prepareDataSource()
prepareSnapshot()
}
func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
config.text = item.name
config.secondaryText = "\(item.calories) Calories" 
config.image = UIImage(named: item.image)
config.imageProperties.maximumSize = CGSize(width: 40, height: 40)
cell.contentConfiguration = config Do It Yourself: Download the thumbnails.zip file from our website
} and add the images to the Assets Catalog. Update the ViewController
return cell
} class with the code in Listing 10-3. Run the application. You should
} see something like Figure 10-6.
func prepareSnapshot() {
The same way we assign attributes to the image, we can assign attributes
to the text. There are two ways to do it. We can assign attributed strings to
the text and secondary text with the attributedText and secondaryAttributedText
properties or define the attributes independently from the textProperties and
secondaryTextProperties properties. For instance, if all we want to do is to
assign different colors to the texts, we can modify the color property of the
TextProperties structure returned by the textProperties and secondaryTextProperties
properties, as shown next.

Listing 10-4: Configuring the texts



func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) 
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
config.text = item.name The UIListContentConfiguration structure also includes type methods that
config.textProperties.color = .systemBlue return a configuration structure with some predefined colors and styles.
config.secondaryText = "\(item.calories) Calories"
For instance, the sidebarCell() method returns a configuration that shows the
config.secondaryTextProperties.color = .systemGray
secondary text in gray. Another useful method is valueCell(), which returns a
config.image = UIImage(named: item.image) configuration that shows the main text on the left and the secondary text
config.imageProperties.maximumSize = CGSize(width: 40, height: 40)
cell.contentConfiguration = config on the right, as show next.
}
return cell
}
Listing 10-5: Implementing standard configurations
} 

func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
Figure 10-7: Elements with custom attributes myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = UIListContentConfiguration.valueCell()
config.text = item.name
config.secondaryText = "\(item.calories) Calories"
cell.contentConfiguration = config
}
return cell

} Background
}
 

Figure 10-8: Standard configuration


In addition to the cell's content, we can also configure the cell's
background. The background's configuration is defined by a
UIBackgroundConfiguration structure. Like the UIListContentConfiguration structure,
the UIBackgroundConfiguration structure includes type methods that return
standard configurations for a cell, including listPlainCell(), listGroupedCell(),
listSidebarCell(), and listAccompaniedSidebarCell(). The class also includes
properties for customization. The following are the most frequently used.

backgroundColor—This property sets or returns the background


color. It's an optional of type UIColor.
backgroundInsets—This property returns the index of the cell.
strokeWidth—This property sets or returns the padding between
the content and the background views. It is a value of type
NSDirectionalEdgeInsets.

strokeColor—This property sets or returns the color of the border
around the background view. It is an optional of type UIColor.
cornerRadius—This property sets or returns a CGFloat value that
determines the radius of the corners.
image—This property sets or returns the background's image. It is an
optional of type UIImage.
imageContentMode—This property sets or returns the mode to
use to layout the image inside the background view. It is the same
ContentMode enumeration provided by the UIView class that we use to
layout the content of a UIImageView. The most useful values are
scaleToFill, scaleAspectFit, and scaleAspectFill.
After the configuration object is defined, we must assign it to the cell. The config.text = item.name
config.image = UIImage(named: item.image)
UITableViewCell class includes the following properties for this purpose. config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
cell.contentConfiguration = config
backgroundConfiguration—This property sets or returns the var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
structure that determines the cell's
UIBackgroundConfiguration backgroundConfig.backgroundColor = .systemGray6
backgroundConfig.backgroundInsets = NSDirectionalEdgeInsets(top: 5, leading: 5,
background configuration. bottom: 5, trailing: 5)
backgroundConfig.cornerRadius = 10
automaticallyUpdatesBackgroundConfiguration—This property cell.backgroundConfiguration = backgroundConfig
sets or returns a Boolean value that indicates whether the }
return cell
background configuration is going to be updated when the state of }
the cell changes. The value by default is true. }
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
The process to define and apply a background configuration to the cell is snapshot.appendSections([.main])
the same we used to configure the content. We must get a standard snapshot.appendItems(AppData.items.map({ $0.id }))
AppData.dataSource.apply(snapshot)
configuration from the type methods, customize it, and then assign it to }
the cell. }

Listing 10-6: Assigning a background configuration to the cell


The diffable data source in Listing 10-6 configures the cell with the main

title, the image, and a gray background. Notice that we also took
import UIKit
advantage of the Table View's rowHeight and separatorStyle to assign a height
class ViewController: UIViewController { of 80 points to the cell and remove the separator. We have also enlarged
@IBOutlet weak var myTable: UITableView!
the images, which are now 60x60 points in size. The result is shown below.
override func viewDidLoad() {
super.viewDidLoad() Figure 10-9: Custom background
myTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
myTable.rowHeight = 80
myTable.separatorStyle = .none

prepareDataSource()
prepareSnapshot()
}
func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()

States

Cells can be in a variety of different states. For instance, if the user taps on
a cell, the cell becomes selected (the cell is in the selected state). If the
user swipes a cell to the side (a gesture used to delete a cell), it is said that
the cell is in the swipe state. The states are reported by Boolean properties
of the UICellConfigurationState structure. Some of the properties available are
isSelected, isHighlighted, isFocused, isDisabled, isEditing, isSwiped, and isExpanded. To
process these states, the UITableViewCell class includes the following
properties.

configurationState—This property returns the cell's current state. It


is a UICellConfigurationState value.

configurationUpdateHandler—This property sets or returns a
Do It Yourself: Update the ViewController class with the code in closure that is executed every time the state of the cell changes. The
Listing 10-6. Run the application. You should see something like closure receives two values: a reference to the cell and a
Figure 10-9. Try to use an image for the background instead of a UICellConfigurationState value to report the cell's current state.
color by modifying the background's image and imageContentMode
properties. With the UICellConfigurationState value, we can define configurations for every
state. For this purpose, the UIListContentConfiguration and
UIBackgroundConfiguration structures define a method that returns a
configuration object for any state we want.

updated(for: State)—This method returns a configuration for the


state specified by the for argument.

The closure assigned to the cell's configurationUpdateHandler property is


executed every time the state of the cell changes and also after it is
created, so we can define the configurations for all the states from it. For
instance, we can update the diffable data source from the previous
example to assign a background to the cell and modify it every time the configuration. The padding around the background view and the round
cell is selected. corners are the same for every state, so we always specify the same values,
but we want the background color to be different when the cell is selected,
Listing 10-7: Defining a background configuration for multiple states so we check if the cell is in the selected state with the isSelected property
 and modify this attribute accordingly. Now the cells are gray but become
func prepareDataSource() { blue when they are selected by the user.
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) Figure 10-10: Different background for each state
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
config.text = item.name
config.image = UIImage(named: item.image)
config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
cell.contentConfiguration = config

cell.configurationUpdateHandler = { cell, state in


var backgroundConfig = UIBackgroundConfiguration.listPlainCell().updated(for: state)
backgroundConfig.backgroundInsets = NSDirectionalEdgeInsets(top: 5, leading: 5,
bottom: 5, trailing: 5) 
backgroundConfig.cornerRadius = 10

if state.isSelected { Do It Yourself: Update the ViewController class introduced in Listing


backgroundConfig.backgroundColor = .systemBlue
} else {
10-6 with the code in Listing 10-7. Run the application and select a
backgroundConfig.backgroundColor = .systemGray6 cell. You should see something like Figure 10-10.
}
cell.backgroundConfiguration = backgroundConfig
}
}
return cell
}
}

The code in Listing 10-7 defines the content configuration as before, but
the background configuration is defined by the closure. The first step is to
get a reference to the configuration for the current state. For this purpose,
we get a standard configuration with the listPlainCell() method and then call
the updated() method on it to get the configuration for the state determined
by the argument received by the closure. Next, we proceed to define the

Cell Subclass
The code in Listing 10-8 defines a subclass of UITableViewCell called FoodCell.

The class includes a property called item to store a value of type ItemsData,
and overrides the updateConfiguration() method to define the cell's
The dequeueReusableCell() method creates a UITableViewCell object from the
configuration. In this case, we define a simple configuration with just the
prototype cell. This object generates a standard cell that we must configure
item's name, but this method can also include a background configuration
to show the values we want. But this code can get long and difficult to
and all the values we need.
maintain. An alternative is to create a UITableViewCell subclass and move all
Now that the configuration of the cell is defined in the cell's subclass, all
the cell configuration from the view controller to this class. To specify the
the diffable data source needs to do is to create the cell from this class and
cell's configuration, the UITableViewCell class defines the following method.
assign the item to the cell's property.

updateConfiguration(using: State)—This method is called every Listing 10-9: Creating a cell from a custom subclass
time the cell needs to update the configuration for a specific state. 
import UIKit
The file for the subclass is created as we did before for view controllers,
class ViewController: UIViewController {
only this time we must select the UITableViewCell class as the superclass. As
@IBOutlet weak var myTable: UITableView!
always, there are no requirements for the name, but it is better to give it a
name related to the cell (for our example, we call it FoodCell). Inside the override func viewDidLoad() {
super.viewDidLoad()
subclass, we must implement the updateConfiguration() method, and also a myTable.register(FoodCell.self, forCellReuseIdentifier: "myCell")
property to store a copy of the data to be displayed, as shown in the prepareDataSource()
prepareSnapshot()
following example. }
func prepareDataSource() {
Listing 10-8: Configuring the cells from a UITableViewCell subclass AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
myTable) { tableView, indexPath, itemID in
 let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as!
import UIKit FoodCell
if let item = AppData.items.first(where: { $0.id == itemID }) {
class FoodCell: UITableViewCell { cell.item = item
var item: ItemsData! }
return cell
override func updateConfiguration(using state: UICellConfigurationState) { }
var config = self.defaultContentConfiguration() }
config.text = item.name func prepareSnapshot() {
self.contentConfiguration = config var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
} snapshot.appendSections([.main])
} snapshot.appendItems(AppData.items.map({ $0.id }))
 AppData.dataSource.apply(snapshot)
}
} Custom Cell


Because we are now creating the cells from our subclass, we must register
the prototype cell with this data type, and cast the value returned by the Sometimes the configuration and elements provided by a standard cell
dequeueReusableCell() method to FoodCell. Once we have the FoodCell object, all may not be enough to satisfy the requirements of our application. There
we need to do in the diffable data source is to assign the current item to are different options available to customize the cells. One alternative is
the item property, so the cell knows which values to display. All the work provided by the UIListContentConfiguration structure. In addition to all the
has been moved from the diffable data source to the cell, but the result is properties defined by this structure to assign attributes to the texts and the
the same as before. image, the structure also includes the following to define the padding
between the elements and their container.
Do It Yourself: Create a new file with a UITableViewCell subclass
called FoodCell. Complete the class with the code in Listing 10-8. imageToTextPadding—This property sets or returns a CGFloat value
Update the ViewController class with the code in Listing 10-9. Run the that determines the padding between the image and the main text.
application. You should see an interface like the one in Figure 10-5. textToSecondaryTextHorizontalPadding—This property sets or
returns a CGFloat value that determines the padding between the text
IMPORTANT: The current template generated by Xcode for and the secondary text when they are displayed side by side.
subclasses of UITableViewCell includes the methods awakeFromNib() and
setSelected(). The awakeFromNib() method is like the viewDidLoad() method.
textToSecondaryTextVerticalPadding—This property sets or
It is called as soon as the cell is loaded, and it is used to initialize it. returns a CGFloat value that determines the padding between the text
The setSelected() method, on the other hand, is called when the cell is and the secondary text when they are displayed on top of each other.
selected. directionalLayoutMargins—This property sets or returns a value
that determines the padding between the elements and the container
view (also known as Content View). It is a value of type
NSDirectionalEdgeInsets.

prefersSideBySideTextAndSecondaryText—This property sets or


returns a Boolean value that determines if the main and secondary
texts are going to be shown side by side.

For example, the following cell is configured with a padding of 50 points on


the left, which displaces the content to the right.

Listing 10-10: Defining the cell's padding Table View cells include a view, called Content View, where all the cell's
 content is placed. When working with configuration structures, the object
import UIKit that represents this view is created from a subclass of UIView that conforms
to the UIContentView protocol. Although we can define our own subclasses,
class FoodCell: UITableViewCell {
var item: ItemsData! as we will see later, UIKit includes a subclass called UIListContentView that
comes with all the elements necessary to present standard values on the
override func updateConfiguration(using state: UICellConfigurationState) {
var config = self.defaultContentConfiguration() screen. This includes the two labels for the text and secondary text, and
config.text = item.name the Image View for the image. This view is automatically pinned to the
config.secondaryText = "\(item.calories) Calories"
config.image = UIImage(named: item.image)
sides of the cell, so the text can use all the space available. There are
config.imageProperties.maximumSize = CGSize(width: 60, height: 60) different ways to add custom elements along with those included by a List
config.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 50, bottom: 0, Content View. An alternative is to create our own UIListContentView object
trailing: 0)
self.contentConfiguration = config and define the constraints to make room for additional views. For instance,
} below is a cell with a List Content View on the left and a custom button on
}

the right.

Figure 10-11: Cell with custom padding Figure 10-12: Cell with custom views

The List Content View is pinned to the top, leading, and bottom of the cell's
Content View with Space constraints, and our custom button is occupying
the space on the right with Space constraints to the List Content View and
the cell's Content View. Now the area available for the text, secondary text,
and image is not the entire cell but the space left by the button.
To produce this layout, we must work with a few new elements. First, we
need to access the cell's Content View. The UITableViewCell class includes the
following property for this purpose.


contentView—This property returns a reference to the cell's
Content View. It is a value of type UIView.
Two properties were added to the FoodCell class to store our List Content
Instead of working with the UIListContentView object created by the View and the button, but the configuration is defined inside the
configuration structure, we must create our own. The UIListContentView class updateConfiguration() method, as before. The UIListContentView class includes
includes the following initializer. the configuration property to set and return the configuration structure
assigned to the view. The property returns a value of type
UIListContentView(configuration: UIContentConfiguration that we must cast to the data type of the configuration
UIListContentConfiguration)—This initializer creates a List Content structure we used to create the view (in this case, UIListContentConfiguration).
View with the configuration defined by the argument. If the casting is successful, we get a copy for the current state, then assign
the item's values to the configuration properties, and finally assign the
The configuration of the cell doesn't change, but now the configuration configuration back to the view.
structure is provided by our custom UIListContentView object. The following Every time the cell calls the updateConfiguration() method to get the
example shows the process we follow to create the cell of Figure 10-12. configuration for the cell, we call our createViews() method to create the
views. This method must create the List Content View and the UIButton to
Listing 10-11: Configuring a cell with a custom List Content View delete the cell, along with all the constraints necessary to achieve the
 layout in Figure 10-12.
import UIKit
Listing 10-12: Defining the views for a custom cell
class FoodCell: UITableViewCell {

private var customView: UIListContentView!
private var customButton: UIButton! func createViews() {
var item: ItemsData! guard contentView.viewWithTag(999) == nil else { return }

override func updateConfiguration(using state: UICellConfigurationState) { customView = UIListContentView(configuration: .subtitleCell())


createViews() customView.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(customView)
if let configuration = self.customView.configuration as? UIListContentConfiguration {
var config = configuration.updated(for: state) customView.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 8).isActive =
config.text = item.name true
config.secondaryText = "Calories: \(item.calories)" customView.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant:
config.image = UIImage(named: item.image) 16).isActive = true
config.imageProperties.maximumSize = CGSize(width: 60, height: 60) customView.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant:
self.customView.configuration = config -8).isActive = true
}
} customButton = UIButton(configuration: .plain(), primaryAction: UIAction(image:
} UIImage(systemName: "trash"), handler: { [unowned self] action in
 self.eraseItem()
}))
customButton.translatesAutoresizingMaskIntoConstraints = false
customButton.tag = 999
self.contentView.addSubview(customButton)

button is pressed, the item is deleted from the model and the cell is
customButton.leadingAnchor.constraint(equalTo: customView.trailingAnchor, constant: 8).isActive
= true removed.
customButton.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant:
-16).isActive = true
Figure 10-13: Cells with custom views
customButton.centerYAnchor.constraint(equalTo: customView.centerYAnchor).isActive = true
customButton.widthAnchor.constraint(equalToConstant: 30).isActive = true
customButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
}

The createViews() method is called every time the state of the cell changes, so
we need to make sure that the views are created only once. For this
purpose, we assign the number 999 to the button's tag property and check
whether a view with that tag already exists inside the cell's Content View. If
no view is found, we proceed to create them.
The views and the constraints are created as before. We create the object,
set the translatesAutoresizingMaskIntoConstraints property to false to stop the
system from defining the constraints for us, add the view to the container
(in this case the cell's Content View), and then assign the constraints.
The action for the button executes a method called eraseItem() which
purpose is to erase the item and update the snapshot to show the change 
on the screen, as shown below.
Do It Yourself: Update the FoodCell class with the code in Listing 10-
Listing 10-13: Deleting a custom cell 11. Add to the class the methods in Listings 13-12 and 13-13. Run
 the application and press the trashcan button to erase a cell.
func eraseItem() {
AppData.items.removeAll(where: { $0.id == item.id })
Custom views added to a standard cell are not part of the cell's
var currentSnapshot = AppData.dataSource.snapshot() configuration and therefore they are not optimized by the system. If we
currentSnapshot.deleteItems([item.id])
AppData.dataSource.apply(currentSnapshot)
want our custom views to be part of the cell's configuration, or to define a
} configuration with custom elements, we must provide our own

configuration structure and UIView subclass. To define the configuration,
UIKit includes the UIContentConfiguration protocol, which requires the
With these changes, the cell now contains a List Content View with a
implementation of the following methods.
standard configuration to present an image, a text, and a secondary text,
along with a button on the right-hand side to delete the item. When the
makeContentView()—This method is called by the cell when it to store a reference to our configuration structure. Along with
configuration
needs a Content View. The method must return a UIView object that this property, we must also create the elements the cell needs to display
conforms to the UIContentView protocol. the values on the screen. For instance, the class in the following example
creates a label and an Image View to show the item's name and image, and
updated(for: State)—This method is called by the cell to get the
adds them to the view with the necessary constraints to place the name on
configuration structure for a state. The method must return the
the left and the image on the right.
configuration structure we want to implement for the state specified
by the for argument. Listing 10-15: Defining a custom Content View

The definition of the configuration structure is simple. We must include class CustomContentView: UIView, UIContentView {
properties to store the values that the cell is going to display and let picture = UIImageView(frame: .zero)
let name = UILabel(frame: .zero)
implement the two required methods.
var configuration: UIContentConfiguration {
Listing 10-14: Defining a custom configuration structure didSet {
newConfiguration()
 }
struct CustomConfig: UIContentConfiguration { }
var name: String! init(configuration: UIContentConfiguration) {
var picture: UIImage! self.configuration = configuration
super.init(frame: .zero)
func makeContentView() -> UIView & UIContentView {
let content = CustomContentView(configuration: self) picture.translatesAutoresizingMaskIntoConstraints = false
return content picture.contentMode = .scaleAspectFit
} self.addSubview(picture)
func updated(for state: UIConfigurationState) -> CustomConfig {
return self let cp1 = picture.widthAnchor.constraint(equalToConstant: 100)
} let cp2 = picture.heightAnchor.constraint(equalToConstant: 100)
} let cp3 = picture.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -16)
 let cp4 = picture.topAnchor.constraint(equalTo: self.topAnchor, constant: 10)
let cp5 = picture.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -10)
cp5.priority = .defaultLow
In this example, the cell is going to show the item's name and image, so we self.addConstraints([cp1, cp2, cp3, cp4, cp5])
include two properties called name and picture to store those values. The
name.translatesAutoresizingMaskIntoConstraints = false
makeContentView() method creates and returns our custom Content View name.numberOfLines = 1
(called CustomContentView in this example), and because the configuration in name.font = UIFont.preferredFont(forTextStyle: .title1)
self.addSubview(name)
this case is going to be the same for every state, the updated() method just
returns the instance of the structure. let cn1 = name.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 16)
For the Content View, we need a subclass of UIView that conforms to the let cn2 = name.trailingAnchor.constraint(equalTo: picture.leadingAnchor, constant: 0)
let cn3 = name.centerYAnchor.constraint(equalTo: picture.centerYAnchor)
UIContentView protocol, which only requirement is a property called self.addConstraints([cn1, cn2, cn3])

With the configuration structure and the Content View ready, we can
newConfiguration()
} finally configure the cell. The process is the same as before, but instead of
func newConfiguration() { using a standard configuration, we implement our CustomConfig structure.
if let config = self.configuration as? CustomConfig {
name.text = config.name
picture.image = config.picture Listing 10-16: Implementing a custom configuration
} 
}
required init?(coder: NSCoder) { import UIKit
fatalError("Error")
} class FoodCell: UITableViewCell {
} var item: ItemsData!

override func updateConfiguration(using state: UICellConfigurationState) {
var config = CustomConfig().updated(for: state)
The CustomContentView class defined in Listing 10-15 is initialized from the config.name = item.name
makeContentView() method in the configuration structure. When this method config.picture = UIImage(named: item.image)
self.contentConfiguration = config
is executed, it sends the configuration structure to the CustomContentView }
initializer. In this initializer, we assign that value to the configuration property }

and proceed to create the views. We create the UIImageView object first, add
it to the Content View, and define the constraints to place it on the right
When the CustomConfig structure is assigned to the cell's contentConfiguration
side of the cell. Then we do the same for the UILabel object, which is pinned
property, the cell calls the makeContentView() method, this method returns
to the left and centered with the image.
the CustomContentView object, the view created by this object is assigned as
The values of these elements change after they are created and each time
the cell's Content View, and the values are shown on the screen.
a new configuration is assigned to the view. To apply these changes, we
included a method called newConfiguration() and call it whenever necessary.
Figure 10-14: Table with custom cells
The method gets the configuration structure by casting the value of the
configuration property to our custom data type (CustomConfig) and updates the
elements with the current values.

IMPORTANT: Notice that the class in Listing 10-15 includes an


additional initializer prefixed by the required keyword. The UIView class
conforms to the NSCoding protocol, which requires this initializer to
be able to encode and decode the view for archiving. We will learn
more about archiving in Chapter 15.
Table View Delegates

Traditionally, the content and configuration of a Table View was provided


by delegate methods. These methods were defined by two protocols:
UITableViewDelegate and UITableViewDataSource. Because now the data and the
configuration are provided by diffable data sources and snapshots, some of
the methods defined by these protocols have become obsolete, but a few
remain useful. The following are some of the methods defined in the
UITableViewDelegate protocol we still need to implement in our applications.

tableView(UITableView, didSelectRowAt: IndexPath)—This


method is called by the table after the user selects a cell. The
 didSelectRowAt argument indicates the cell's location.
tableView(UITableView, didDeselectRowAt: IndexPath)—This
Do It Yourself: Add the CustomConfig structure introduced in Listing method is called by the table when a cell is deselected. The
10-14 to the FoodCell.swift file (below the FoodCell class). Do the didDeselectRowAt argument indicates the cell's location.
same for the CustomContentView class of Listing 10-15. Update the tableView(UITableView, heightForHeaderInSection: Int)—This
FoodCell class with the code in Listing 10-16. Run the application. You method is called by the table to get the height of the header for the
should see the table of Figure 10-14. section at the index indicated by the heightForHeaderInSection
argument. The method must return a CGFloat value with the header's
height.
tableView(UITableView, heightForFooterInSection: Int)—This
method is called by the table to get the height of the footer for the
section at the index indicated by the heightForFooterInSection
argument. The method must return a CGFloat value with the footer's
height.
tableView(UITableView,
leadingSwipeActionsConfigurationForRowAt: IndexPath)—
This method is called by the table to get the actions to display on the

left side of each row when they are in edition mode. It must return a called EditingStyle included in the UITableViewCell class that indicates the
UISwipeActionsConfiguration object containing the UIContextualAction objects type of operation performed. The possible values are delete and insert.
that define the actions. sectionIndexTitles(for: UITableView)—This method is called by
tableView(UITableView, the table to get the strings that represent the sections in the table’s
trailingSwipeActionsConfigurationForRowAt: IndexPath)— index. The method must return an array of strings with the values for
This method is called by the table to get the actions to display on the the indexes.
right side of each row when they are in edition mode. It must return a tableView(UITableView, sectionForSectionIndexTitle: String,
UISwipeActionsConfiguration object containing the UIContextualAction objects at: Int)—This method is called by the table to get the section
that define each action. corresponding to an index title. The method must return an integer
value with the section’s index. The sectionForSectionIndexTitle
Although the data is provided by a diffable data source, the table still relies argument is a string with the index’s title, and the at argument is the
on methods defined by the UITableViewDataSource protocol for advanced
position where the title is located on the index.
configuration. The following are the most frequently used.
tableView(UITableView, moveRowAt: IndexPath, to:
tableView(UITableView, titleForHeaderInSection: Int)—This IndexPath)—This method is called when the user moves a cell to a
method is called by the table to get the title for the header of the different position on the table. It is implemented along with the
section indicated by the titleForHeaderInSection argument. The edition tools provided by Table Views. The moveRowAt argument is
method must return a String value with the header’s title. the index path in which the cell is currently located and the to
argument is the index path where the cell is going to be placed.
tableView(UITableView, titleForFooterInSection: Int)—This
method is called by the table to get the title for the footer of the tableView(UITableView, canMoveRowAt: IndexPath)—This
section indicated by the titleForFooterInSection argument. The method is called when the user tries to move a cell to a different
method must return a String value with the footer’s title. position. The method must return a Boolean value to indicate if the
action is allowed. The canMoveRowAt argument indicates the cell's
tableView(UITableView, canEditRowAt: IndexPath)—This
location.
method is called by the table to know if the user is allowed to edit a
row. The method must return a Boolean value to indicate if the row at
The methods defined by the UITableViewDelegate protocol are easy to
the path determined by the canEditRowAt argument can be edited.
implement. All we need to do is to define an object that conforms to the
tableView(UITableView, commit: EditingStyle, forRowAt: protocol (usually the view controller) and assign it as the table's delegate,
IndexPath)—This method is called by the table when the user inserts so all the protocol methods are called on it, as shown next.
or deletes a row. It is implemented along with the edition tools
provided by Table Views. The commit argument is an enumeration Listing 10-17: Selecting multiple items
 }

import UIKit

class ViewController: UIViewController, UITableViewDelegate { In this example, we make the view controller conform to the
@IBOutlet weak var myTable: UITableView! UITableViewDelegate protocol and assign it as the Table View's delegate using
override func viewDidLoad() { the delegate property provided by the UITableView class. Assigning the value
super.viewDidLoad() self to this property, the view controller becomes the table's delegate, and
myTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
myTable.delegate = self we can proceed to implement the protocol methods we need. In this case,
prepareDataSource() we implement the tableView(UITableView, didSelectRowAt:) method to modify the
prepareSnapshot()
}
value of the item's selected property every time the user selects a cell.
func prepareDataSource() { First, we get the item's identifier from the cell's index path with the
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView: itemIdentifier() method provided by the data source. The itemIdentifier()
myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) method takes the IndexPath value received by the protocol method, which
if let item = AppData.items.first(where: { $0.id == itemID }) { contains the indexes of the section and the row where the selected cell is
cell.configurationUpdateHandler = { cell, state in
var config = cell.defaultContentConfiguration().updated(for: state)
located, and returns the corresponding identifier for the item in the cell.
config.text = item.name With the identifier, we get the item from the model, as before, and modify
config.secondaryText = "\(item.calories) Calories"
its values. In this example, we toggle the value of the item's selected
config.image = UIImage(named: item.image)
config.imageProperties.maximumSize = CGSize(width: 40, height: 40) property with the toggle() method. If the value was true, it becomes false, and
cell.contentConfiguration = config vice versa, indicating whether the item is selected or not.
cell.accessoryType = item.selected ? .checkmark : .none Because we are modifying the value of the item's selected property every
} time a cell is selected, we don't need the table to show the selected cell
}
return cell
anymore, so we deselect it with the Table View's deselectRow() method. To
} show the user which items are currently selected and which not, we add
} an accessory view to the cell. The UITableViewCell class includes the
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>() accessoryType property to define the type of accessory we want to show.
snapshot.appendSections([.main]) When we want to display the accessory, we must assign one of the values
snapshot.appendItems(AppData.items.map({ $0.id }))
AppData.dataSource.apply(snapshot) available or the value none to hide it. In our example, we use the checkmark
} type. If the value of the selected property is true, we assign this value to the
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
accessoryType property and a checkmark is shown on the screen for that
if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) {
if let item = AppData.items.first(where: { $0.id == itemID }) { item, otherwise, the value assigned to the property is none and the
item.selected.toggle() accessory is hidden. The result is shown below.
}
}
tableView.deselectRow(at: indexPath, animated: true) Figure 10-15: Selected cells highlighted with Checkmark accessories
}

10.2 Table Views in Navigation Controllers


Tables in iOS are meant to work along with other scenes to provide more
functionality. The addition of rows, for example, requires a separate scene
to provide the fields and buttons the user needs to insert the information.
This is the reason why we frequently see applications with tables
embedded in Navigation Controllers. Navigation Controllers present scenes
with a transition that feels more natural to the user and helps establish the
relationship between the table and the rest of the scenes. Figure 10-16
shows what a scene with a table looks like after it is embedded in a
Navigation Controller.

Figure 10-16: Table View embedded in a Navigation Controller


Do It Yourself: Update the ViewController class with the code in


Listing 10-17. In this example, we are using a standard cell, so the
custom cell defined in the previous section is no longer required.
Run the application and select multiple cells. You should see the
checkmarks indicating the selected cells.

A common function provided by tables is the possibility to select a row to


see more information related to the item. For instance, considering the
previous examples, we may add a scene that presents the nutritional
values associated with an item every time the user taps on a cell.

Figure 10-17: Table with a Detail View



import UIKit

class ViewController: UIViewController, UITableViewDelegate {


@IBOutlet weak var myTable: UITableView!
var selected: ItemsData!

override func viewDidLoad() {


super.viewDidLoad()
myTable.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
 myTable.delegate = self
prepareDataSource()
prepareSnapshot()
The scene that presents the additional information is called Detail View,
}
because it shows details about the selected item. To create a Detail View, func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
we must add a new scene to the Storyboard and connect a Show segue if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) {
if let item = AppData.items.first(where: { $0.id == itemID }) {
from the table's view controller to the new scene (Figure 9-17). For our selected = item
example, we have populated the Detail View with a label, an Image View, }
}
and a Text View to display the values (Figure 10-18, right). performSegue(withIdentifier: "showDetails", sender: self)
}
Figure 10-18: Detail View in the Storyboard override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetails" {
let controller = segue.destination as! DetailViewController
controller.selected = selected
}
}
func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
myTable) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) {
 var config = cell.defaultContentConfiguration()
config.text = item.name
cell.contentConfiguration = config
A Table View inside a Navigation Controller works the same way as before. }
To access the detail view when a cell is selected, we must implement the return cell
}
tableView(UITableView, didSelectRowAt:) method (see Listing 10-17), trigger the }
segue with the performSegue() method (see Listing 9-6), and send the data to func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
the Detail View with the prepare() method (see Listing 9-4). snapshot.appendSections([.main])
snapshot.appendItems(AppData.items.map({ $0.id }))
AppData.dataSource.apply(snapshot)
Listing 10-18: Opening the Detail View when a cell is selected
}

} The DetailViewController class checks whether the selected property contains an



item or not and then assigns the values to the Outlets to display the item
on the screen.
The tableView(UITableView, didSelectRowAt:) method in Listing 10-18 gets the
selected item with the itemIdentifier() method, as we did before, and assigns
it to a property called selected. After this reference is stored, we can call the
Do It Yourself: Embed the initial scene in a Navigation Controller
performSegue() method to open the Detail View (the segue was identified
and assign the title Groceries to it. You should see something like
with the name "showDetails"). Figure 10-16. Add another scene to the Storyboard. Create a Show
When a segue is triggered, the system calls the prepare() method. In our segue from the scene with the table to the second scene (Figure 9-
example, we implement this method to get a reference to the view 17) and identify the segue with the string "showDetails". Add a label,
controller of the Detail View and pass the item stored in the selected an Image View, and a Text View to the second scene to get an
property to the selected property of the destination controller. interface like the one in Figure 10-18. Create a subclass of
Accordingly, the view controller for the Detail View must include the selected UIViewController for the second scene called DetailViewController. Create
property along with Outlets for each of the elements on the interface to the Outlets for the elements in the scene with the names used in
show the information on the screen. We call this view controller Listing 10-19. Update the ViewController class with the code in Listing
DetailViewController.
10-18 and complete the DetailViewController class with the code in
Listing 10-19. Run the application and select a row. You should see
Listing 10-19: Showing the details of the selected row
something like Figure 10-17.

import UIKit
Once selected, the rows remain in that state until the user selects another
class DetailViewController: UIViewController { row. This means that when the user comes back from the Detail View, the
@IBOutlet weak var titleItem: UILabel!
@IBOutlet weak var imageItem: UIImageView! selected row is still visible. As we have seen before, to deselect a row we
@IBOutlet weak var nutritionItem: UITextView! have to call the Table View’s deselectRow() method (see Listing 10-19). Where
var selected: ItemsData!
we call this method depends on what we want to achieve with our app.
override func viewWillAppear(_ animated: Bool) { The only thing we need to remember is that once the row is deselected,
super.viewWillAppear(animated)
the indexPathForSelectedRow property returns nil (because no row is selected
if selected != nil {
titleItem.text = selected.name anymore), so we always must deselect the cell after the value of its row is
imageItem.image = UIImage(named: selected.image) no longer required. The following example implements the viewWillAppear()
nutritionItem.text = "Calories: \(selected.calories)"
} method to deselect the cells when the user comes back from the Detail
} View (the viewDidLoad() method is only called when the scene is loaded, but
}

the viewWillAppear() method is called every time the scene is about to be
shown on the screen).
Listing 10-20: Deselecting the selected row Adding Rows


override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let path = myTable.indexPathForSelectedRow { Navigation controllers not only provide a natural transition between views
myTable.deselectRow(at: path, animated: true)
} but also a bar where we can put buttons to let the user manage the table
} and the information it contains (see Chapter 9). A very common feature

added to the table from this bar is a button that opens a view with a form
to add new items to the list. There is even a predefined style for Bar Button
Do It Yourself: Add the method in Listing 10-20 to the view
Items called Add that creates a button with a plus sign. Figure 10-19,
controller of Listing 10-18. Run the application and select a row. Go
below, shows the Add button inserted on the right side of the bar and a
back to the Table View. The selected row should be deselected. new scene with an input field to add items to the model. The new scene is
connected to the Add button with a Show segue.

Figure 10-19: Scene to insert new items

The addition of new items is very simple, we provide the form for the user
to insert the data and then store it in the model (In our case, this is the
items array in the AppData structure). To keep it simple, we have included
only a Text Field to let the user insert the name of the item (Figure 10-19,
right). The rest of the information is filled with placeholders ("noimage"
and "Not Defined"). The following is the code for the view controller of the
new scene (we call it AddItemViewController).

Listing 10-21: Adding items to the model


import UIKit NSDiffableDataSourceSnapshotobject and provide all the values again as we did
class AddItemViewController: UIViewController { in the prepareSnapshot() method before.
@IBOutlet weak var newItem: UITextField!

@IBAction func saveItem(_ sender: UIButton) {


Do It Yourself: Add a Bar Button Item to the Navigation Bar over
var text = newItem.text! the Table View of our previous example. Add a new scene to the
text = text.trimmingCharacters(in: .whitespaces)
if text != "" {
Storyboard. Connect the bar button to the new scene with a Show
let lower = text.lowercased() segue. Add a label, a Text Field, and a button to the scene, as shown
let final = lower.capitalized
in Figure 10-19. Create a subclass of UIViewController called
let itemData = ItemsData(final, "noimage", 0, false) AddItemViewController and assign it to the new scene. Connect the Text
AppData.items.append(itemData)
Field to the AddItemViewController class with an Outlet called newItem
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>() and the Save button with an Action called saveItem(). Complete this
snapshot.appendSections([.main])
snapshot.appendItems(AppData.items.map({ $0.id })) view controller with the code in Listing 10-21. Run the application,
AppData.dataSource.apply(snapshot, animatingDifferences: false) press the plus button, insert a text, and press the Save button. The
navigationController?.popViewController(animated: true) new item should be added to the table.
}
}
}

The view controller includes an Outlet for the Text Field called newItem and
an Action for the button called saveItem(). When the button is pressed, the
saveItem() method trims the text in the Text Field with the trimmingCharacters()
method (see Listing 5-53), and then compares it with an empty string. If
the user’s input is not empty, the code capitalizes the string, creates the
ItemsData structure to store the information for the new item, and adds it to
the items array with the append() method.
This process modifies the model, but we still need to update the snapshot
to provide the new information to the data source and update the table. In
this case, we must consider that the model sorts the data in alphabetical
order and therefore we can't just append the new item to the current
snapshot. Items appended to an existent snapshot are added at the end of
the list, but the position for the new item may be different, depending on
the name inserted by the user. In consequence, we must create a new
Deleting Rows
Listing 10-22: Removing the selected item


@IBAction func deleteItem(_ sender: UIButton) {
The same way we can add items we can also delete them. The process is as if selected != nil {
simple as deleting the item from the model and updating the snapshot, but AppData.items.removeAll(where: { $0.id == selected.id })
there are several ways to allow the user to do it. An alternative is to include var currentSnapshot = AppData.dataSource.snapshot()
a button in the Detail View that the user can press to delete the item. In currentSnapshot.deleteItems([selected.id])
that scene, we know what item was selected by the user because we have AppData.dataSource.apply(currentSnapshot)

a reference in the selected property, and the user also knows what item is navigationController?.popViewController(animated: true)
going to be deleted because the item's information is on the screen. Figure }
}
10-20 shows the Detail View of our example modified to include a Delete 
button below the thumbnail.
The removeAll(where:) method implemented in the code in Listing 10-22 reads
Figure 10-20: Delete button the value of the id property of each item in the array and removes the item
which value matches the value of the id property of the selected item.
After the item is removed from the model, we also remove it from the
current snapshot with the deleteItems() method, and the scene is closed.

Do It Yourself: Add a button called Delete to the Detail View of the


previous example (Figure 10-20). Connect the button to an Action in
the DetailViewController class called deleteItem(). Complete the method
with the code in Listing 10-22. Run the application. Select a row and
press the Delete button. The item should be removed from the table.

IMPORTANT: The user should be warned every time the code is


 going to delete data from the model. UIKit offers the UIAlertController
class to create views for this purpose. We will study how to create
The Delete button must be connected to an Action in the DetailViewController Alert Views in Chapter 13.
class to delete the item from the model and close the scene. To delete the
item from the items array, we can use the value of the id property and the In addition to any custom techniques, Table Views also provide built-in
removeAll(where:) method provided by the Array structure, as in the following
functionality to allow the user to remove items from the table. The table
example.

includes an editing mode that when activated shows a button on the left to
delete the item and a button on the right to confirm the action.

Figure 10-21: Table in editing mode

 

The process to activate and deactivate this mode involves the isEditing The purpose of the Edit button is to activate or deactivate the edition
property, the setEditing() method (both from the UITableView class), and two mode depending on its current state. To determine the state, we can read
methods defined in the UITableViewDataSource protocol: the the isEditing property, and to set the new state we must call the setEditing()
tableView(UITableView, canEditRowAt:) method to tell the table whether a row method, as in the following example.
can be edited or not, and the tableView(UITableView, commit:, forRowAt:) method
to process the deletion. Listing 10-23: Activating and deactivating the editing mode
The first step is to provide a way for the user to decide when to activate or 

deactivate the mode. For our example, we have added a Bar Button Item to @IBAction func editItems(_ sender: UIBarButtonItem) {
if myTable.isEditing {
the Navigation Bar called Edit. myTable.setEditing(false, animated: true)
} else {
myTable.setEditing(true, animated: true)
Figure 10-22: Edit button }
}

The editItems() method in Listing 10-23 checks the value of the isEditing
property and sets the mode to false if the property is true, or to true if the
property is false. When the value is set to true, the Table View calls the
tableView(UITableView, canEditRowAt:) method on the data source delegate and
allows the user to edit the rows or not, based on the value returned by the the action performed by the user. If the value is delete, which means the
method (true or false). user wants to delete the row, we delete the item from the model and the
Data source delegate methods are implemented by the diffable data snapshot following the same procedure used before in the
source, so if we want to provide our own implementation, we must define DetailViewController class (see Listing 10-22). Notice that this time we
a subclass of the UITableViewDiffableDataSource class. For this example, we reference the data source with self because we are inside a
have created a Swift file called MyDataSource.swift to store our subclass UITableViewDiffableDataSource subclass.
called MyDataSource. To implement this subclass in our code, we must define the dataSource
property in our model with the MyDataSource data type and create the
Listing 10-24: Implementing the data source delegate methods diffable data source from this subclass.

import UIKit Listing 10-25: Implementing our data source subclass

class MyDataSource: UITableViewDiffableDataSource<Sections, ItemsData.ID> {
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool func prepareDataSource() {
{ AppData.dataSource = MyDataSource(tableView: myTable) { tableView, indexPath, itemID in
return true let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
} if let item = AppData.items.first(where: { $0.id == itemID }) {
override func tableView(_ tableView: UITableView, commit editingStyle: var config = cell.defaultContentConfiguration()
UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { config.text = item.name
if editingStyle == UITableViewCell.EditingStyle.delete { cell.contentConfiguration = config
if let itemID = self.itemIdentifier(for: indexPath) { }
AppData.items.removeAll(where: { $0.id == itemID }) return cell
}
var currentSnapshot = self.snapshot() }
currentSnapshot.deleteItems([itemID]) 
self.apply(currentSnapshot)
}
}
The prepareDataSource() method in Listing 10-25 replaces the same method in
} our ViewController class, but the only difference is that now instead of
} creating the diffable data source from the UITableViewDiffableDataSource class,

we do it from our subclass (This example assumes that we have defined
The MyDataSource subclass in Listing 10-24 implements the two methods we the dataSource property in our model with the data type MyDataSource, as in
var dataSource: MyDataSource!).
need to manage the editing mode. First, we override the
tableView(UITableView, canEditRowAt:) method and return the value true. This
tells the table that all the rows are editable. Next, we override the Do It Yourself: Add a Bar Button Item with the title "Edit" to the
tableView(UITableView, commit:, forRowAt:) method. This method is called when Navigation Bar at the top of the Table View (Figure 10-22). Connect
the user presses the Delete button on the table (see Figure 10-21, right). the button to an Action in the ViewController class called editItems() and
The method receives an EditingStyle value with a value that corresponds to complete the method with the code in Listing 10-23. Create a new

Swift file called MyDataSource.swift for the code in Listing 10-24. UISwipeActionsConfiguration(actions: [UIContextualAction])
Open the ApplicationData.swift file and change the data type of the —This initializer returns an object with the actions provided by the
dataSource property to MyDataSource (var dataSource: MyDataSource!). argument and the configuration defined by its properties.
Replace the prepareDataSource() method in the ViewController class with performsFirstActionWithFullSwipe—This is a Boolean property
the method in Listing 10-25. Run the application and press the Edit that determines if the first action included in the object is going to be
button. You should see the edition buttons appear on the table's left- executed when the user performs a full swipe.
hand side. Press one of these buttons. The row should displace to
the left and reveal the Delete button on the right. Press this button To define the buttons and actions, we must create an object of the
to delete the row. UIContextualAction class. The following are its initializer and properties.

IMPORTANT: Tables include a hidden feature that simplifies the UIContextualAction(style: Style, title: String?, handler:
deletion of rows and the execution of custom tasks. If the delegate Closure)—This initializer returns a UIContextualAction object with the
methods are implemented, as we did in the MyDataSource subclass, definition of a button. The style argument defines the style of the
the feature is activated, which allows the user to uncover the Delete button. It is an enumeration called Style included in the
button by swiping the row to the left. If this feature is enough for UIContextualAction class. The values available are normal (color gray) and
your application, there is no need to add an Edit button to activate destructive (color red). The title argument is a string with the text to be
or deactivate the mode, all you need to do is implement the
shown on the button, and the handler argument is the closure that is
delegate methods.
going to be executed when the action is performed. The closure
receives three values: a reference to the action, a reference to the
We can also show our own buttons on the left and right side of the rows
implementing the methods tableView(UITableView, button’s view, and another closure that takes a Boolean to declare
trailingSwipeActionsConfigurationForRowAt:) and tableView(UITableView, whether the action was successful or not.
leadingSwipeActionsConfigurationForRowAt:) defined by the UITableViewDelegate title—This property sets or returns a string with the button’s title.
protocol. These methods are called when the user swipes a row to one side backgroundColor—This property sets or returns a UIColor object
or the other and are in charge of defining the buttons displayed by the
with the button’s background color.
table and the actions they perform. For this purpose, UIKit includes two
classes: the UISwipeActionsConfiguration class, to provide the actions and image—This property sets or returns a UIImage object with the
configuration values, and the UIContextualAction class to define each button button’s image.
and action. The following are the initializer and property provided by the
UISwipeActionsConfiguration class. If we implement these methods in our view controller instead of the
tableView(UITableView, commit:, forRowAt:) method in the diffable data source,
the Delete button is replaced by our own buttons. For instance, in the
following example we create our own Delete button with a blue This method is defined by the UITableViewDelegate protocol, so we must
background and a different title. implement it in the ViewController class and make the class conform to the
protocol, as done before (see Listing 10-18). The method replaces the
Listing 10-26: Creating our own actions tableView(UITableView, commit:, forRowAt:) method implemented in the
 MyDataSource subclass of Listing 10-24, but we still have to implement the
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: tableView(UITableView, canEditRowAt:) method in the data source to tell the
IndexPath) -> UISwipeActionsConfiguration? { table that the user is allowed to edit the rows.
let button = UIContextualAction(style: .normal, title: "Remove", handler: { (action, view,
completion) in
if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) { Do It Yourself: Add the method in Listing 10-26 to the ViewController
AppData.items.removeAll(where: { $0.id == itemID })
class. Remove the tableView(UITableView, commit:, forRowAt:) from the
var currentSnapshot = AppData.dataSource.snapshot() MyDataSource class. Run the application and swipe a row to the left.
currentSnapshot.deleteItems([itemID])
AppData.dataSource.apply(currentSnapshot) You should see a blue button with the title "Remove". Press the
}
button to remove the row.
completion(true)
})
button.backgroundColor = UIColor.systemBlue

let config = UISwipeActionsConfiguration(actions: [button])


config.performsFirstActionWithFullSwipe = false
return config
}

When the user swipes a row to the left, the table calls the method in
Listing 10-26 to know which buttons to display. In this example, we create
an action called button with the style normal, the title "Remove", and a
closure that performs the same action as before (it gets the item's
identifier, it removes it from the model, and updates the snapshot). Notice
that at the end of the closure we call the completion handler received by
the third argument with the value true to indicate that the action was
performed successfully, otherwise the button is not removed. Finally, we
create a new UISwipeActionsConfiguration object with this action, set its
performsFirstActionWithFullSwipe property to false so the user cannot perform
the action with a full swipe, and return it.

Moving Rows if let itemOrigin = self.itemIdentifier(for: sourceIndexPath), let itemDestination =


self.itemIdentifier(for: destinationIndexPath) {
 var currentSnapshot = self.snapshot()
if sourceIndexPath.row > destinationIndexPath.row {
currentSnapshot.moveItem(itemOrigin, beforeItem: itemDestination)
The edition mode also allows users to move cells to a different position on } else {
currentSnapshot.moveItem(itemOrigin, afterItem: itemDestination)
the table. This option is presented as an icon on the right side of the cells. }
To activate it, we must implement two methods defined by the self.apply(currentSnapshot, animatingDifferences: false)
}
UITableViewDataSource protocol: the tableView(UITableView, moveRowAt:, to:)
}
method to modify the data when a cell is moved to a different position, }
and the tableView(UITableView, canMoveRowAt:) method to tell the table that the 

user is allowed to move the row.


The cells are automatically placed in their new position every time the user In the subclass of Listing 10-27, we return true from the tableView(UITableView,
canEditRowAt:) method to allow the user to edit the table, and from the
performs the action, but we also need to make sure that the new order is
tableView(UITableView, canMoveRowAt:) method to allow the user to move the
reflected in the model and the snapshot, so the next time the table loads
its content, it shows the cells in the order determined by the user. The rows. Then, we implement the tableView(UITableView, moveRowAt:, to:) method
to sort the items in the model and the snapshot. This method receives two
following example presents a possible implementation of these delegate
methods. values indicating the original position of the cell and the position where
the user is trying to move it. First, we check that these values are not
equal. If they are equal, which means the cell was moved to the same
Listing 10-27: Reorganizing the model when a cell is moved

position, we return nothing and finish the process, otherwise, we proceed
to modify the model. We get a reference to the item being moved, remove
import UIKit
it from the array with the remove(at:) method, and then add it again in the
class MyDataSource: UITableViewDiffableDataSource<Sections, ItemsData.ID> { new position with the insert(at:) method. After that, we get the identifiers of
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
{ the items in both positions and modify the snapshot. To move the item to
return true the new position, we must call different methods depending on the order.
}
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) ->
If the new position is below the current position, we move the item with
Bool { the moveItem(beforeItem:) method. Otherwise, we must use the
return true moveItem(afterItem:) method. The snapshot is finally applied with no
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to animation. This is to avoid conflicts with the table's natural animation.
destinationIndexPath: IndexPath) {
guard sourceIndexPath != destinationIndexPath else { return }
Figure 10-23: Editing mode to move cells
let item = AppData.items[sourceIndexPath.row]
AppData.items.remove(at: sourceIndexPath.row)
AppData.items.insert(item, at: destinationIndexPath.row)
Modifying Rows

To allow users to modify the items, we must provide a form where they can
insert the new values. For our example, we have added a button called Edit
 Item to the Detail View (Figure 10-24, number 1) and a scene connected to
this button with a Show segue. The scene presents the same interface used
To move a cell, the user must drag the cell from the icon to the new before to add items (Figure 10-24, number 2).
position. In the example of Figure 10-23, we moved the Cookies cell to the
second row. When the change is performed, the Table View calls the Figure 10-24: Button and interface to modify items
delegate methods of Listing 10-27 and the item corresponding to the cell is
moved to an index that reflects its new position on the table.

Do It Yourself: Update the MyDataSource class with the code in


Listing 10-27. Run the application and press the Edit button. You
should see the icons to move the cells. Drag a cell to a new position
from one of these icons. The table should reorder the cells, as shown 
in Figure 10-23.
The view controller for the new scene must receive a reference to the item
IMPORTANT: The model we have been using so far automatically to be modified, show the current value in the Text Field for reference, and
organizes the items in alphabetical order (see Listing 10-1). If we then assign the new value inserted by the user to the name property of the
want the user to decide the order of the items, we must use a model ItemsData structure when the Save button is pressed. We called this view
that allows it. To test the example of Listing 10-27, you must delete controller EditItemViewController.
the didSet() method of the items property in the ApplicationData structure
(var items: [ItemsData] = []). This makes sure that the model delivers the Listing 10-28: Modifying an item
items in the order they are stored. 
import UIKit

class EditItemViewController: UIViewController {


@IBOutlet weak var newName: UITextField!
var selected: ItemsData!

override func viewDidLoad() {


super.viewDidLoad()

if selected != nil { controller.selected = selected


newName.text = selected.name }
newName.becomeFirstResponder() }
} 
}
@IBAction func saveItem(_ sender: UIButton) {
var text = newName.text! Do It Yourself: Add a Bar Button Item to the Detail View with the
text = text.trimmingCharacters(in: .whitespaces) title "Edit Item" (Figure 10-24, number 1). Add a new scene to the
if text != "" {
let lower = text.lowercased() Storyboard with a label, a Text Field, and a Save button (Figure 10-
let final = lower.capitalized 24, number 2). Connect the Edit Item button to this new scene with
selected.name = final
a Show segue called "showEditItem". Create a new subclass of
var currentSnapshot = AppData.dataSource.snapshot() UIViewController called EditItemViewController and assign it to the new
currentSnapshot.reloadItems([selected.id])
AppData.dataSource.apply(currentSnapshot) scene. Connect the Text Field to this view controller with an Outlet
called newName and the Save button with an Action called saveItem().
navigationController?.popViewController(animated: true)
} Complete the view controller with the code in Listing 10-28. Add the
}
prepare() method in Listing 10-29 to the DetailViewController class. Run
}
 the application. Select an item on the table, press the Edit Item
button, and change the name. You should see the changes in the
In this view controller, we define the selected property again to receive the Detail View and the table.
item from the Detail View. Then, in the viewDidLoad() method, we assign the
item's name to the Text Field and make it the first responder with the
becomeFirstResponder() method. This automatically activates the keyboard and
shows the cursor on the Text Field for the user to start typing.
When the Save button is pressed, the saveItem() method trims the text,
capitalizes it, and assigns it to the name property of the selected item.
Finally, the snapshot is updated with the reloadItems() method and the view
is closed.
To tell the view controller which item to edit, we must send it from the
Detail View with the prepare() method, as shown next.

Listing 10-29: Sending the selected item



override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showEditItem" {
let controller = segue.destination as! EditItemViewController
10.3 Table View Controller

Table Views that take up the entire screen are very common in mobile
applications. To simplify the creation of these tables, the UIKit framework
provides a subclass of the UIViewController class called UITableViewController,
which includes a full-screen table. The class has several advantages over
adding the Table View ourselves. It conforms to the Table View protocols
by default, the controller is fully integrated with Navigation Controllers,
search bars and controls to refresh the data, and it even offers another
type of cells called Static Cells that allow us to present the cells as they are
defined in the Storyboard. The Library includes the Table View Controller
option to add this view controller to the interface.

Figure 10-25: Table View Controller option in the Library

 To manage the Table View, we must create a subclass of the


UITableViewController class and assign it to the scene in the Storyboard. The
A Table View Controller looks like any other scene but with a full-screen file is created as any other file but with the UITableViewController class as the
table inside. The difference is that the Table View is integrated into the superclass. Also, the Table View includes a prototype cell. We can use this
scene and its size cannot be changed. prototype cell or register the cell as we have done so far in our examples.

Figure 10-26: Table View Controller in the Storyboard Listing 10-30: Managing the table from a subclass of UITableViewController

import UIKit

class MyTableViewController: UITableViewController {


override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
prepareDataSource()
prepareSnapshot()
}

func prepareDataSource() { have to deselect the cell anymore, as we did in the example of
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
tableView) { tableView, indexPath, itemID in Listing 10-20. The UITableViewController class offers a Boolean property
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) called clearsSelectionOnViewWillAppear to activate or deactivate this
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
feature.
config.text = item.name
cell.contentConfiguration = config
}
return cell
}
}
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(AppData.items.map({ $0.id }))
AppData.dataSource.apply(snapshot)
}
}

The UITableViewController class includes a property called tableView with a


reference to the Table View. This saves us from creating the Outlet for the
table. The rest of the configuration and code work the same way as before.

Do It Yourself: Create a new project. Delete the initial scene and


add a Table View Controller from the Library (Figure 10-25). Select
the scene, open the Attributes Inspector panel, and activate the
option Is Initial View Controller to designate it as the initial scene.
Create a new file with a subclass of UITableViewController called
MyTableViewController and assign it to the scene. Complete the class
with the code in Listing 10-30. This example assumes that you have
included into the project the model from Listing 10-1. Run the
application. You should see a table with the items in the model listed
by name.

IMPORTANT: A Table View Controller automatically deselects a cell


when the table is shown on the screen. For this reason, we do not
Refresh Control endRefreshing()—This method tells the control that the process is
over.

The UITableView class inherits the following property from the UIScrollView
Table View Controllers include a feature usually provided by modern
class to assign the control to the table.
applications that allow the user to refresh the data by scrolling down the
table. When the user keeps scrolling down the table, a spinning wheel
appears at the top to indicate that the system is refreshing the data. refreshControl—This property sets or returns a reference to the
Refresh Control associated to the table.
Figure 10-27: Refresh Control
When the user scrolls down the table, the Table View Controller shows the
Refresh Control and fires a valueChanged event to indicate to the code that it
should perform the corresponding tasks. Therefore, to respond to the
control, we must add an action to the Refresh Control for this event with
the addAction(for:) method, as shown in the following example.

Listing 10-31: Presenting a Refresh Control

The graphics are created by the Table View Controller, but UIKit defines the
import UIKit
UIRefreshControl class to manage the control. The class includes the following
properties and methods to configure the control and report the state of class MyTableViewController: UITableViewController {
var refresh: UIRefreshControl!
the process.
override func viewDidLoad() {
super.viewDidLoad()
tintColor—This property sets or returns a UIColor value that defines tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
the control's color. prepareDataSource()
prepareSnapshot()
attributedTitle—This property sets or returns the text shown in the
control. It is a value of type NSAttributedString. refresh = UIRefreshControl()
refresh.addAction(UIAction(handler: { [unowned self] action in
isRefreshing—This is a Boolean property that indicates if the control self.refreshTable()
}), for: .valueChanged)
is in the process of refreshing the data.
let text = AttributedString("Refreshing Table")
beginRefreshing()—This method tells the control that the process refresh.attributedTitle = NSMutableAttributedString(text)
of refreshing the data was initiated. tableView.refreshControl = refresh
}
func refreshTable() {
prepareSnapshot()

refresh.endRefreshing() 10.4 Search


}
func prepareDataSource() { 
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
tableView) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) Table Views can manage thousands of rows, and therefore users can
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration() sometimes have a hard time finding what they are looking for. For this
config.text = item.name reason, the UIKit framework defines the UISearchController class. This class
cell.contentConfiguration = config
} creates a controller that includes the functionality necessary to allow the
return cell user to search for values in the model and present them on the screen. The
}
} controller must be created with the following initializer.
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
UISearchController(searchResultsController:
snapshot.appendItems(AppData.items.map({ $0.id })) UIViewController?)—This initializer creates a search controller
AppData.dataSource.apply(snapshot)
} associated with the view controller indicated by the argument. The
} argument is a reference to the view controller we want to use to

present the information or nil if we want to use the current view
The view controller in Listing 10-31 initializes the control, adds an action to controller.
it, and then assigns it to the table. The closure calls the refreshTable() method
to respond to the event. When the user uncovers the control, the The UISearchController object creates a search bar with a text field and
valueChanged event is fired and the method is executed. In this example, we buttons to let the user perform the search. The following are the
just reload the snapshot with the prepareSnapshot() method, but this is where properties included in the object to access the bar and configure the
we usually access a server to download information, or read a database. controller.
After the update, we call the endRefreshing() method to indicate to the
control that the process is over. searchBar—This property returns a reference to the UISearchBar
object created by the controller to present the search bar.
Do It Yourself: Update the MyTableViewController class from the obscuresBackgroundDuringPresentation—This property sets or
previous example with the code in Listing 10-31. Run the application returns a Boolean value that indicates if the background is obscured
and scroll down the table to activate the control. This example during the presentation of the search bar.
defines an attributed text for the control, so you will see something
hidesNavigationBarDuringPresentation—This property sets or
like the right picture of Figure 10-27.
returns a Boolean value that determines whether the Navigation Bar
should be hidden during the presentation of the search bar.
following is the code required by our model to filter the data and store the
The controller also includes the searchResultsUpdater property to store a result.
delegate object that receives the value introduced by the user, performs
the search, and updates the interface. The object assigned to this property Listing 10-32: Filtering the data
must conform to the UISearchResultsUpdating protocol and implement the 

following method. var searchValue: String = ""


var filteredItems: [ItemsData] {
get {
updateSearchResults(for: UISearchController)—This method is if searchValue.isEmpty {
return items
called by the search controller to process the value inserted by the } else {
user. var list = items.filter( { (item) -> Bool in
let value1 = item.name.lowercased()
let value2 = searchValue.lowercased()
To present the search bar, we must assign the controller to the return value1.hasPrefix(value2)
})
Navigation Item of the Navigation Bar. The UINavigationItem class offers the list.sort(by: { (value1, value2) in value1.name < value2.name })
following properties for this purpose. return list
}
}
searchController—This property sets or returns a reference to the }

search controller we want to present in the Navigation Bar.
hidesSearchBarWhenScrolling—This property sets or returns a The code in Listing 10-32 defines the two properties we must add to our
Boolean value that indicates whether the search bar will be hidden model. The searchValue property stores the value searched by the user, and
when the table is scrolled. filteredItems is a computed property that always returns an array with the
items that match the search. Because the snapshot is going to be updated
The process is simple. We must embed a Table View Controller in a with the values from the filterItems property every time the user inserts or
Navigation Controller, create the search controller with the UISearchController deletes a character, we cannot compare entire words, we must look for
initializer, declare the Table View Controller as the delegate, and values in the items array the first letters of which match the characters
implement the method of the UISearchResultsUpdating protocol to process the inserted by the user. The hasPrefix() method introduced in Chapter 3 is
input. perfect for this situation. The method compares two strings and returns
Search controllers provide tools for the user to perform the search, but it is true if the first string is found at the beginning of the second string (the

up to us to take this input and select the items in our model that match the method is case sensitive, so we lowercased both strings before comparing).
term. There are different ways to do it. When working with arrays, one Because the model manages the search, the view controller is simple. We
alternative is to use the filter() method introduced in Chapter 3. The need to create the search controller and conform to the
UISearchResultsUpdating protocol to process the input.

Listing 10-33: Processing the input from the search controller The first thing we do in this view controller is to create a search controller.
 In the initializer, we specify the nil value to declare this view controller as
import UIKit the one responsible for showing the results. The view controller is also
assigned as the delegate of the search controller through the
class MyTableViewController: UITableViewController, UISearchResultsUpdating {
override func viewDidLoad() { searchResultsUpdater property, and the search controller is finally assigned to
super.viewDidLoad() the searchController property of the Navigation Item to show the search bar
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
prepareDataSource() on the screen.
prepareSnapshot() When the user inserts a character in the search bar, the search controller
calls the updateSearchResults(for:) method with a reference to the search
let searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self controller. From this reference, we read the value inserted by the user,
searchController.obscuresBackgroundDuringPresentation = false assign it to the searchValue property in the model, and then update the
navigationItem.searchController = searchController
} snapshot with the prepareSnapshot() method. (Notice that the snapshot is
func prepareDataSource() { created with the values of the filteredItems property instead of the items
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
tableView) { tableView, indexPath, itemID in
property.) The result is shown below.
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) { Figure 10-28: Search controller in the Navigation Bar
var config = cell.defaultContentConfiguration()
config.text = item.name
cell.contentConfiguration = config
}
return cell
}
}
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(AppData.filteredItems.map({ $0.id }))

AppData.dataSource.apply(snapshot)
}
func updateSearchResults(for searchController: UISearchController) {
Do It Yourself: Embed the Table View Controller in a Navigation
if let text = searchController.searchBar.text { Controller. Insert the title Groceries for the scene. Add the code in
AppData.searchValue = text.trimmingCharacters(in: .whitespaces)
prepareSnapshot()
Listing 10-32 to the ApplicationData structure (below the definition of
} the items property). Update the MyTableViewController class with the
}
} code in Listing 10-33. Run the application. You should see the search
 bar at the top. Insert a value. The table should be updated with the
items that match the value, as shown in Figure 10-28.
IMPORTANT: In the example of Listing 10-33, we assigned the Search Bar
value false to the obscuresBackgroundDuringPresentation property to not let

the controller obscure the interface when the bar is selected, but
you can also assign the value false to the
The bar created by the controller is an object of the UISearchBar class, which
hidesNavigationBarDuringPresentation property to keep the Navigation Bar
includes several methods and properties for configuration. In the example
visible and take advantage of the hidesSearchBarWhenScrolling property
of Listing 10-33, we used the text property to get the value inserted by the
of the Navigation Item to keep the search bar visible when the table
user, but there are more available.
is scrolled up.
text—This property sets or returns the text in the field. It is an
optional of type String.
placeholder—This property sets or returns the placeholder. It is an
optional of type String.
tintColor—This property sets or returns the color of the bar’s
elements. It is an optional of type UIColor.

IMPORTANT: The UISearchBar class also conforms to the


protocol and implements its properties to configure
UITextInputTraits
the keyboard and some features for the Text Field. For more
information, see UITextField in Chapter 5.

The UISearchBar class can also designate its own delegate object to report
the state of the bar. The delegate must conform to the UISearchBarDelegate
protocol and implement its methods.

searchBarTextDidBeginEditing(UISearchBar)—This method is
called by the bar on its delegate when the edition begins (the user
taps on the bar to perform a search).
searchBarTextDidEndEditing(UISearchBar)—This method is
called by the bar on its delegate when the edition ends.

searchBarCancelButtonClicked(UISearchBar)—This method is 
called by the bar on its delegate when the Cancel button is pressed. var searchValue: String = ""
var selectedButton: Int = 0
searchBar(UISearchBar, var filteredItems: [ItemsData] {
get {
selectedScopeButtonIndexDidChange: Int)—This method is if searchValue.isEmpty {
called by the bar on its delegate when a button in the scope bar is return items
} else {
pressed. The button's index is provided by the second argument. var list = items.filter( { (item) -> Bool in
if selectedButton == 0 {
let value1 = item.name.lowercased()
The last method works with a feature provided by search bars called Scope
let value2 = searchValue.lowercased()
Bar (see Figure 10-29, below). This is an additional bar displayed below the return value1.hasPrefix(value2)
search bar with selectable buttons (like Segmented Controls). When a } else if selectedButton == 1 {
if let maximum = Int(searchValue), item.calories < maximum {
button is selected, the others are automatically deselected. The UISearchBar return true
class includes the following properties to create and configure these }
}
buttons. return false
})
list.sort(by: { (value1, value2) in value1.name < value2.name })
showsScopeBar—This property is a Boolean value that determines return list
whether the scope bar is displayed or not. When the value is set to }
}
true, an additional bar is shown at the bottom of the Search Bar with
}
buttons to select the scope of the search. 

scopeButtonTitles—This property sets or returns an array of strings


The code in Listing 10-34 defines the properties we need to add to our
with the title for the buttons.
model to manage the search and the scope bar. This includes a property
selectedScopeButtonIndex—This property sets or returns the called selectedButton to store the index of the active button, so we know what
index of the selected button in the scope bar. It is of type Int. to search for. In the closure for the filteredItems property, we filter the items
according to the value of this property. If the value is 0, which means the
The bar assigns an index to every button and every time a button is first button is active, we filter the items by title, but if the value is 1, the
selected it calls the searchBar(UISearchBar, selectedScopeButtonIndexDidChange:) filter gets the number inserted by the user and only returns true if the
method on its delegate with the button’s index, which we can use to refine item's calories are lower than that value.
the search. For example, we can search items by title or by the number of The view controller must implement the searchBar(UISearchBar,
calories. The following is the update required in our model to filter the selectedScopeButtonIndexDidChange:) method to store the index of the selected
items depending on the button selected by the user. button in the model and adapt the interface accordingly.

Listing 10-34: Expanding the model to work with a scope bar Listing 10-35: Adding a scope bar
 }
}
import UIKit func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange
selectedScope: Int) {
class MyTableViewController: UITableViewController, UISearchResultsUpdating, AppData.selectedButton = selectedScope
UISearchBarDelegate { prepareSnapshot()
override func viewDidLoad() { searchBar.placeholder = selectedScope == 0 ? "Search Product" : "Maximum Calories"
super.viewDidLoad() searchBar.text = ""
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell") }
prepareDataSource() }
prepareSnapshot() 

let searchController = UISearchController(searchResultsController: nil)


searchController.searchResultsUpdater = self After creating the search controller, the code in Listing 10-35 gets a
searchController.obscuresBackgroundDuringPresentation = false reference to the search bar to modify its properties. We first declare the
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false view controller as the bar’s delegate, then assign the string “Search
Product” as placeholder, and finally create two buttons for the scope bar.
let searchBar = searchController.searchBar
searchBar.delegate = self To respond to the user, we have implemented the searchBar(UISearchBar,
searchBar.placeholder = "Search Product" selectedScopeButtonIndexDidChange:) method. This method is called when a
searchBar.showsScopeBar = true
searchBar.scopeButtonTitles = ["Names", "Calories"]
button is pressed in the scope bar. Here, we assign the index of the active
searchBar.selectedScopeButtonIndex = 0 button to the model’s selectedScope property to notify the model the type of
} information it must look for and update the snapshot. After that, we
func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView: change the bar’s placeholder to help the user identify the value that the
tableView) { tableView, indexPath, itemID in Text Field is expecting and clean the bar to show all the original values on
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) {
the screen. Consequently, if the Names button is selected, the model tries
var config = cell.defaultContentConfiguration() to match the value with the names of the items, but when the Calories
config.text = item.name
button is selected, we compare the value to the number of calories in each
cell.contentConfiguration = config
} item.
return cell
}
} Figure 10-29: Scope bar in action
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(AppData.filteredItems.map({ $0.id }))
AppData.dataSource.apply(snapshot)
}
func updateSearchResults(for searchController: UISearchController) {
if let text = searchController.searchBar.text {
AppData.searchValue = text.trimmingCharacters(in: .whitespaces)

prepareSnapshot()

10.5 Sections self.section = section


}
 }
struct ApplicationData {
var dataSource: MyDataSource!
Tables organize the information in sections. The model used so far declares var sections: [Sections] = []
var items: [ItemsData] = []
the sections with an enumeration called Sections. The enumeration only
includes one value called main because our examples only required one init() {
sections.append(contentsOf: [Sections(name: "B"), Sections(name: "C"), Sections(name: "D"),
section, but we can declare more. An alternative is to add more values to Sections(name: "G"), Sections(name: "J"), Sections(name: "L"), Sections(name: "M"),
the Sections enumeration, but if the sections are dynamic (they are Sections(name: "O"), Sections(name: "P"), Sections(name: "T"), Sections(name: "Y")])
generated according to the information inserted by the user), we should items.append(contentsOf: [ItemsData("Bagels", "bagels", 250, false, "B"),
ItemsData("Brownies", "brownies", 466, false, "B"), ItemsData("Butter", "butter", 717, false, "B")])
define our own data type to identify them. items.append(contentsOf: [ItemsData("Cheese", "cheese", 402, false, "C"), ItemsData("Coffee",
There are different ways to organize this information. For this example, we "coffee", 0, false, "C"), ItemsData("Cookies", "cookies", 502, false, "C")])
items.append(contentsOf: [ItemsData("Donuts", "donuts", 452, false, "D")])
have decided to create and additional class for the sections and store the items.append(contentsOf: [ItemsData("Granola", "granola", 471, false, "G")])
values in two arrays, one for the sections and another for the items. items.append(contentsOf: [ItemsData("Juice", "juice", 23, false, "J")])
items.append(contentsOf: [ItemsData("Lemonade", "lemonade", 40, false, "L"),
ItemsData("Lettuce", "lettuce", 15, false, "L")])
Listing 10-36: Storing the data in sections items.append(contentsOf: [ItemsData("Milk", "milk", 42, false, "M")])
 items.append(contentsOf: [ItemsData("Oatmeal", "oatmeal", 68, false, "O")])
items.append(contentsOf: [ItemsData("Potatoes", "potato", 77, false, "P")])
import UIKit items.append(contentsOf: [ItemsData("Tomatoes", "tomato", 18, false, "T")])
items.append(contentsOf: [ItemsData("Yogurt", "yogurt", 59, false, "Y")])
class Sections: Identifiable { }
var id: UUID = UUID() }
var name: String var AppData = ApplicationData()

init(name: String) {
self.name = name
} The Sections class includes the id property to identify each section and the
} name property to store the section's name. We have also incorporated an
class ItemsData: Identifiable {
var id: UUID = UUID() additional property to the ItemsData class to store the name of the section
var name: String to which the item belongs. The property to store the sections is called
var image: String
sections and the property to store the items is called items. After the
var calories: Int
var selected: Bool properties are defined, we initialize them with a few values to test the
var section: String
model.
init(_ name: String, _ image: String, _ calories: Int, _ selected: Bool, _ section: String) { The model classifies the data, but the sections are defined by the snapshot,
self.name = name as shown next.
self.image = image
self.calories = calories
self.selected = selected Listing 10-37: Defining a snapshot with sections
 only returns the values that are different from nil. This means that we can
import UIKit process the items and include in the result only the identifiers of those that
belong to the current section (if the item doesn't belong to the section, we
class MyTableViewController: UITableViewController {
override func viewDidLoad() { return nil to ignore it).
super.viewDidLoad() This organizes the items in sections, but we still need to provide the titles
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
prepareDataSource() for the headers of each section. An alternative is to implement the
prepareSnapshot() tableView(UITableView, titleForHeaderInSection:) method of the UITableViewDataSource
}
func prepareDataSource() {
protocol in our UITableViewDiffableDataSource subclass, as in the following
AppData.dataSource = MyDataSource(tableView: tableView) { tableView, indexPath, itemID in example.
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration() Listing 10-38: Declaring the sections' titles
config.text = item.name 
cell.contentConfiguration = config
} import UIKit
return cell
class MyDataSource: UITableViewDiffableDataSource<Sections.ID, ItemsData.ID> {
}
} override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) ->
func prepareSnapshot() { String? {
return AppData.sections[section].name
var snapshot = NSDiffableDataSourceSnapshot<Sections.ID, ItemsData.ID>()
snapshot.appendSections(AppData.sections.map({ $0.id })) }
for section in AppData.sections { }

let itemIDs = AppData.items.compactMap({ value in
return value.section == section.name ? value.id : nil
}) The tableView(UITableView, titleForHeaderInSection:) method is called by the table
snapshot.appendItems(itemIDs, toSection: section.id)
} every time it needs the title of a section. In this method, we read the value
AppData.dataSource.apply(snapshot) of the section's name property and return it. The table takes this value and
}
}
turns it into the section's title.
 When items are split in sections, we can take advantage of the multiple
styles available for Table Views. Figure 10-30, below, shows what we see
The sections are now identified by the value of the id property, so we must when we run our application with the Table View's Style option set to
define the NSDiffableDataSourceSnapshot object with the Sections.ID data type, as Plain, Grouped and Inset Grouped.
we did before for the items. The section ids are added to the snapshot with
the appendSections() method, as always, but this time the items are added to Figure 10-30: Items in sections
the sections they belong to with the appendItems(toSection:) method. To get
the list of items per section, we iterate through the sections array and filter
the items with the compactMap() method. This method is similar to map(), but

dequeueReusableHeaderFooterView(withIdentifier: String)—
This method returns a UITableViewHeaderFooterView object to represent a
header or footer view. The object is created from the prototype with
the identifier defined by the withIdentifier argument.

 To further optimize performance, header and footer views are configured


with configuration structures. The UIListContentConfiguration structure defines
several type methods to return standard configurations for headers and
Do It Yourself: Update the model with the code in Listing 10-36
footers, including plainHeader(), groupedHeader(), sidebarHeader(), plainFooter(),
and the MyTableViewController class with the code in Listing 10-37.
groupedFooter(), prominentInsetGroupedHeader(), and
Create a Swift file called MyDataSource.swift for the code in Listing
extraProminentInsetGroupedHeader().
10-38. Select the Table View and modify the Style option from the There are two methods defined in the UITableViewDelegate protocol we need
Attributes Inspector panel to configure the table with the style you to implement to provide a custom header. The tableView(UITableView,
want. Run the application. You should see the table of Figure 10-30. viewForHeaderInSection:) method creates and returns the view for the header,
(As of this writing, the Style option applies different styles to the and the tableView(UITableView, heightForHeaderInSection:) method specifies the
table. Select and deselect the options until you get the style you header's height. The following view controller shows how to implement
want.) these methods in our example.

Like cells, section headers are created from prototypes. After the Listing 10-39: Configuring the headers
tableView(UITableView, titleForHeaderInSection:) method returns the title for the 
header, the Table View creates a header view from a prototype view and import UIKit
shows it on the screen. Although this is enough for some applications, we class MyTableViewController: UITableViewController {
can define and configure our own prototype. The UITableView class includes override func viewDidLoad() {
super.viewDidLoad()
the following methods to register a prototype and create a view from it.
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
tableView.register(UITableViewHeaderFooterView.self,
register(AnyClass?, forHeaderFooterViewReuseIdentifier: forHeaderFooterViewReuseIdentifier: "myHeader")
prepareDataSource()
String)—This method registers a prototype header or footer view. prepareSnapshot()
The fist argument is the class that is going to be used to create the }
func prepareDataSource() {
cells (the UITableViewHeaderFooterView class or a subclass of it), and the AppData.dataSource = MyDataSource(tableView: tableView) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
forHeaderFooterViewReuseIdentifier argument specifies the string if let item = AppData.items.first(where: { $0.id == itemID }) {
the table is going to use to identify this prototype view. var config = cell.defaultContentConfiguration()
config.text = item.name
cell.contentConfiguration = config
}
return cell
}
}
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections.ID, ItemsData.ID>()
snapshot.appendSections(AppData.sections.map({ $0.id }))
for section in AppData.sections {
let itemIDs = AppData.items.compactMap({ value in
return value.section == section.name ? value.id : nil
})
snapshot.appendItems(itemIDs, toSection: section.id)
}
AppData.dataSource.apply(snapshot)
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) ->
UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "myHeader")!

var config = UIListContentConfiguration.prominentInsetGroupedHeader()


config.text = AppData.sections[section].name
config.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 0, leading: 15, bottom: 0,
trailing: 0)
header.contentConfiguration = config
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -

> CGFloat {
return 40
}
Do It Yourself: Update the MyTableViewController class with the code
} in Listing 10-39. The header's view and title are now provided by the

tableView(UITableView, viewForHeaderInSection:) method, so you can
remove the method added to the MyDataSource class in Listing 10-38.
The first thing we do in this view controller is to register the prototype view
Run the application. You should see something like Figure 10-31.
for the headers with the identifier "myHeader". Using this identifier, we
create the header views with the configuration provided by the
prominentInsetGroupedHeader() method and the appropriate margins and height
When working with sections, we can also provide an index on the table's
for an Inset Grouped table. The result is show below. right-hand side to help the user navigate through the items. The
UITableViewDataSource protocol includes the sectionIndexTitles() method to

Figure 10-31: Table with custom headers provide an array with the titles for the indexes, and the tableView(UITableView,
sectionForSectionIndexTitle:, at:) method to specify the section to which the

titles belong. The following example implements these methods in our


MyDataSource subclass to create the indexes from the names of the sections.

Listing 10-40: Showing the index



import UIKit

class MyDataSource: UITableViewDiffableDataSource<Sections.ID, ItemsData.ID> {


override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) ->
String? {
return AppData.sections[section].name
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
let titles = AppData.sections.map({ $0.name })
return titles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String,
at index: Int) -> Int {
if let index = AppData.sections.firstIndex(where: { $0.name == title }) {
return index
}
return 0
}
}

The sectionIndexTitles() method in Listing 10-40 creates an array with the 


values of the items' name properties and returns it. This gives the table the
list of titles for the sections, but because we could include titles for Do It Yourself: Update the MyDataSource class with the code in
sections that do not yet exist on the table, we also implement the Listing 10-40. Run the application. You should see the index on the
tableView(UITableView, sectionForSectionIndexTitle:, at:) method to provide the right, as illustrated in Figure 10-32. Tapping on these buttons scrolls
indexes of the sections corresponding to each title. This method is called the table to the corresponding section.
every time the table needs to know which section a title belongs to, so we
search for the title in the sections array with the firstIndex(where:) method and
return it. The result is shown below (number 1).

Figure 10-32: Table indexes


Deleting Sections
The MyDataSource method in Listing 10-41 includes the protocol method

necessary to return the section's name, and the two we need to edit the
rows (see Listing 10-24). When the Delete button is pressed, we get the
When working with sections, we must take care of creating new sections
item and section IDs, and then delete the item, as we did before, but now
and deleting those that are not required anymore. The process always
we get the number of items in the item's section and if there are no items
depends on how our model is organized. For instance, the following
left, we proceed to delete the section in the model and the snapshot.
example shows how to delete a section when all the section's items have
been removed.
Do It Yourself: Update the MyDataSource class with the code in
Listing 10-41. Run the application, swipe a cell to the left to reveal
Listing 10-41: Deleting empty sections

the Delete button, and press the button to delete the cell. If there
import UIKit
are no items left in the section, the section is deleted as well.

class MyDataSource: UITableViewDiffableDataSource<Sections.ID, ItemsData.ID> {


override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) ->
String? {
return AppData.sections[section].name
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
{
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle:
UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
if let itemID = self.itemIdentifier(for: indexPath), let sectionID = self.sectionIdentifier(for:
indexPath.section) {
AppData.items.removeAll(where: { $0.id == itemID })

var currentSnapshot = self.snapshot()


currentSnapshot.deleteItems([itemID])
if currentSnapshot.numberOfItems(inSection: sectionID) <= 0 {
AppData.sections.removeAll(where: { $0.id == sectionID })
currentSnapshot.deleteSections([sectionID])
}
self.apply(currentSnapshot)
}
}
}
}

10.6 Static Tables


Table View Controllers offer the possibility to create static tables. Static
tables present a fixed number of cells and therefore are particularly useful
when we have a limited number of options or predefined data to display.
To turn the table of a Table View Controller into a static table, we must
select it and change the Content option in the Attributes Inspector panel to
Static Cells.

Figure 10-33: Option to change the type of cells



From this point on, the cells and their content are designed in the
Once the option is selected, the table replaces the prototype cells with Storyboard by dragging the elements from the Library to the cells, as we do
three custom cells. with any other view. We can add new cells by dragging the Table View Cell
option from the Library, and new sections may be added by modifying the
Figure 10-34: Empty static cells in an Inset Grouped table value of the Section option from the Attributes Inspector panel. When a
section is selected, the Attributes Inspector panel offers options to specify
the titles for the section's header and footer.
If the scene in the Storyboard is not tall enough to fit all the cells we need,
we can set a different size from the Simulated Size option in the Size
Inspector panel, as we did for Scroll Views in Chapter 7. Figure 10-35,
below, shows a possible design. For this example, we changed the table’s
style to Inset Grouped, use custom cells, and added labels and a section
with a single cell to present an Image View.

Figure 10-35: Definition of static cells


let now = Date.now
dateLabel.text = now.formatted(date: .abbreviated, time: .omitted)
picture.image = UIImage(named: "Toronto")
}
}

In the view controller for a static table, we do not have to declare a


snapshot or a data source because everything has been defined in the
Storyboard, but we can assign new values to the elements in the cells. In
Listing 10-42, we assign strings to the labels, a date, and an image to the
Image View. The result is shown below.

Figure 10-36: Application with a static table

The custom cells for a static table do not need their own UITableViewCell
subclass; all we need to do is to connect the elements directly to Outlets in
the view controller. The following is the code for the UITableViewController
subclass necessary to control the interface of Figure 10-35.

Listing 10-42: Defining the content of static cells



import UIKit

class MyTableViewController: UITableViewController {


@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var placeLabel: UILabel!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var picture: UIImageView!

override func viewDidLoad() {


super.viewDidLoad()
titleLabel.text = "Toronto's Waterfront"
placeLabel.text = "Toronto"

Modify the sizes of the cells from the Size Inspector panel to fit the
elements and add labels to the cells of the first section and an Image
View to the cell of the second section. Assign the necessary
constraints to get the interface in Figure 10-35. Select the sections to
change their titles. Create a subclass of UITableViewController called
MyTableViewController and assign it to the scene. Complete the class
with the code in Listing 10-42 and connect the elements on the cells
to the Outlets in the code. Run the application. You should see
something like Figure 10-36.

Do It Yourself: Create a new project. Download the image


Toronto.jpg from our website and add it to the Assets Catalog.
Delete the scene and add a Table View Controller from the Library
(Figure 10-25). Select the scene, open the Attributes Inspector panel,
and activate the option Is Initial View Controller. Select the table and
change the value of the Content option to Static Cells. Assign the
value 2 to Sections to add another section to the table. Select the
cells you don't need and press the Delete button to remove them.
CHAPTER 11 - COLLECTION VIEWS 11.1 Collection Views

Collection Views are like Table Views, with the difference that they can
present the information with a customizable layout. They come with a grid-
like layout by default, but we can modify it or replace it entirely to present
the views any way we want.

Figure 11-1: Collection Views

Collection Views are defined by the UICollectionView class. As any other


views, they may be created from code or added to a scene from the option
in the Library.

UICollectionView(frame: CGRect, collectionViewLayout:


UICollectionViewLayout)—This initializer creates a UICollectionView
object with the frame specified by the frame argument and the layout
object provided by the collectionViewLayout argument.

Figure 11-2: Collection View option in the Library

scrollToItem(at: IndexPath, at: ScrollPosition, animated: Bool)


—This method scrolls the Collection View to show on the screen the
cell at the position indicated by the first at argument. The second at

argument is a property of a structure called ScrollPosition included in
Although we can use this option to add a Collection View to a scene, the the UICollectionView class that indicates how the Collection View is going
Library also includes an option to add a scene to the Storyboard that to scroll. The properties available are top, bottom, left, right,
comes with a full-screen Collection View. centeredVertically, and centeredHorizontally. And the animated argument is a
Boolean value that indicates if the process is going to be animated
Figure 11-3: Collection View Controller option in the Library

The following are some of the properties and methods included in the
UICollectionView class to configure the view.

allowsSelection—This property is a Boolean value that determines


whether the selection of cells is allowed or not.
allowsMultipleSelection—This property is a Boolean value that
determines whether multiple selection of cells is allowed or not.
collectionViewLayout—This property sets or returns the layout
object in charge of setting the layout for the cells. It is an object of a
subclass of UICollectionViewLayout.
indexPathsForSelectedItems—This property returns an array of
IndexPath structures with the indexes of the selected cells.
deselectItem(at: IndexPath, animated: Bool)—This method
deselects the cell at the position indicated by the at argument. The
animated argument indicates if the process is going to be animated.
Collection View Cells using argument. The for argument indicates the cell's location, and
the item argument is the data to be shown by the cell.

The views displayed by a Collection View are also called cells. Cells in a
Collection View are created from the UICollectionViewCell class, but they do
not include any standard elements by default. Therefore, to create a cell,
we must define a subclass of the UICollectionViewCell class and add our own
views to the cell's Content View. The class includes the following property
for this purpose.

contentView—This property returns the cell's Content View. All the


elements added to customize the cell are incorporated as subviews of
this view.

Just like the cells in a Table View, these cells are created from prototype
cells. To register a prototype cell, the UICollectionView class defines the
CellRegistration structure.

CellRegistration(handler: Closure)—This initializer registers and


configures a prototype cell. The handler argument is a closure that is
called every time a cell is required. The closure receives three values:
a reference to the cell, the index path with the location of the cell,
and a reference to the data that must be shown by the cell.

The following is the method provided by the UICollectionView class to create


cells from a prototype cell.

dequeueConfiguredReusableCell(using: CellRegistration, for:


IndexPath, item: Item?)—This method returns a cell with the
configuration defined by the CellRegistration structure specified by the

Collection View Delegate Data Source


 

Collection Views can designate a delegate to respond to user interaction. As with tables, the snapshot is created from the NSDiffableDataSourceSnapshot
UIKit includes the UICollectionViewDelegate protocol to define this delegate class, but UIKit defines a specific class to create the diffable data source for
object. The following are the methods we need to implement to respond Collection Views called UICollectionViewDiffableDataSource. The class provides
to the selection of a cell. the following initializer.

collectionView(UICollectionView, didSelectItemAt: IndexPath) UICollectionViewDiffableDataSource(collectionView:


—This method is called by the Collection View on its delegate when a UICollectionView, cellProvider: Closure)—This initializer creates a
cell is selected. data source for the Collection View specified by the collectionView
collectionView(UICollectionView, didDeselectItemAt: argument. The cellProvider argument is a closure that is called every
IndexPath)—This method is called by the Collection View on its time a cell is required. The closure receives three values: a reference
delegate when a cell is deselected. to the Collection View, the index path with the location of the cell,
and a reference to the data that must be shown by the cell.
11.2 Implementing Collection Views registered and configured from code. For this purpose, we must define our
own subclass of UICollectionViewCell.

All the subclass needs to do is to create the views we want to use to show
the values. In the following example, we define a subclass called FoodCell
As already mentioned, Collection Views can be initialized from code, added
with an Image View to show the item's pictures.
to a scene from the Library, or included with a Collection View Controller.
Because Collection Views usually filled the screen, the latter is the
Listing 11-1: Creating a Collection View cell
recommended option. Figure 11-4 shows what a Collection View Controller

looks like in the Storyboard.
import UIKit

Figure 11-4: Collection View Controller class FoodCell: UICollectionViewCell {


let picture = UIImageView()

override init(frame: CGRect) {


super.init(frame: frame)
picture.translatesAutoresizingMaskIntoConstraints = false
picture.contentMode = .scaleAspectFit
self.contentView.addSubview(picture)

picture.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 8).isActive = true


picture.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive
= true
picture.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8).isActive
= true
picture.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8).isActive
= true
}
required init?(coder: NSCoder) {
fatalError("Error")
}
}

 The FoodCell class includes a property called picture to store the Image View.
This is the property that we access later to configure the cell with the
The Collection View included with the scene comes with a prototype cell image of each item. When the cell is initialized, we set up the Image View
(the square at the top of the scene in Figure 11-4). Before iOS 14, this was with Space constraints to pin it to the sides of the Content View.
the recommended way to create our prototype cells, but modern cells are The steps to set up the Collection View are the same as for Table Views,
but the process is different. The configuration of the cell is done by the
closure assigned to the CellRegistration structure, so all that is left to do for

the diffable data source is to create the cell with the Next, we create the diffable data source. Because the cell is already
dequeueConfiguredReusableCell() method and return it. (For this example, we are configured by the CellRegistration structure, all we need to do here is to call
implementing the same model used for Table Views in Chapter 10, Listing the dequeueConfiguredReusableCell() method with a reference to the
10-1. The only difference is that now the diffable data source must be configuration structure to create the cell and return it.
defined with the UICollectionViewDiffableDataSource class.) The cells, the diffable data source, and the snapshot define the content of
the Collection View, but how the cells are going to be laid out is defined by
Listing 11-2: Configuring a Collection View a layout object. Collection Views come with a default layout called Flow
 layout. This layout is configured to position the cells in a grid, which means
import UIKit the cells will be displayed in as many rows and columns as the available
space allows.
class MyCollectionViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad() Figure 11-5: Flow layout
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
indexPath, itemID in
if let item = AppData.items.first(where: { $0.id == itemID }) {
cell.picture.image = UIImage(named: item.image)
}
}
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
(collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath,
item: itemID)
}
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main]) 
snapshot.appendItems(AppData.items.map({ $0.id }))
AppData.dataSource.apply(snapshot)
} Do It Yourself: Create a new project. Remove the initial scene and
} add a Collection View Controller from the Library. Select the scene

and activate the option Is Initial View Controller from the Attributes
The CellRegistration structure is generic. To create an instance, we specify the Inspector panel. Create a file with a subclass of UICollectionViewCell
cell's data type (FoodCell) and the data type we use to identify the values in called FoodCell. Update this class with the code in Listing 11-1. Create
the model (ItemsData.ID). The initializer creates the prototype cell and a Swift file called ApplicationData.swift with the model in Chapter
provides the closure to configure it. This closure is called later every time a 10, Listing 10-1. Define the dataSource property in the model with the
new cell is required by the Collection View. In our example, we get the item UICollectionViewDiffableDataSource class (var dataSource:
from the item's ID, and assign the item's image to the cell's picture property. UICollectionViewDiffableDataSource<Sections, ItemsData.ID>!). Create a
subclass of UICollectionViewController called MyCollectionViewController.
Update this class with the code in Listing 11-2. Download the } else {
backgroundConfig.backgroundColor = .systemBackground
thumbnails from our website and add them to the Assets Catalog. }
Run the application. You should see something like Figure 11-5. cell.backgroundConfiguration = backgroundConfig
}
Rotate the device to see how the layout adapts the cells to the space }
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
available.
(collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
Collection View cells can be selected, but they do not show any indication itemID)
}
to the user (the background color doesn't change). If we want to change }
the background every time a cell is selected or deselected, we must apply a func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
background configuration. Like Table View cells, Collection View cells snapshot.appendSections([.main])
include the configurationUpdateHandler property to take a closure that we can snapshot.appendItems(AppData.items.map({ $0.id }))
AppData.dataSource.apply(snapshot)
use to configure the cell every time the state changes, and the
}
backgroundConfiguration property to assign the background configuration to }
the cell. The implementation is the same used for Table Views (see 

Background in Chapter 10).


Figure 11-6: Different background for selected cells
Listing 11-3: Configuring the background of a Collection View cell

import UIKit

class MyCollectionViewController: UICollectionViewController {


override func viewDidLoad() {
super.viewDidLoad()
prepareDataSource()
prepareSnapshot()
}
func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
indexPath, itemID in
if let item = AppData.items.first(where: { $0.id == itemID }) {
cell.picture.image = UIImage(named: item.image)
}
cell.configurationUpdateHandler = { cell, state in
var backgroundConfig = UIBackgroundConfiguration.listPlainCell().updated(for: state)
backgroundConfig.cornerRadius = 10

if state.isSelected { 
backgroundConfig.backgroundColor = .systemGray5

Flow Layout

How the cells are laid out in a collection view is determined by an object
created from a subclass of the UICollectionViewLayout class. Although we can
create our own subclass to define any layout we want, UIKit includes a
subclass called UICollectionViewFlowLayout that provides a highly customizable
grid-like layout called Flow layout. This is the layout assign to the Collection
View by default and the one we have been using so far. In our examples,
we implemented this layout with standard settings, but the class includes
the following properties for configuration.

scrollDirection—This property sets or returns the direction of the


scroll. It is an enumeration called ScrollDirection included in the
UICollectionView class. The values available are vertical and horizontal (with
vertical set by default).

minimumInteritemSpacing—This property sets or returns a


CGFloat value that determines the space between cells. In vertical
scrolling, the value determines the space between cells in the same
row, but in horizontal scrolling it determines the space between items
in the same column.
minimumLineSpacing—This property sets or returns a CGFloat
value that determines the space between cells. In vertical scrolling,
the value determines the space between rows, but in horizontal
scrolling it determines the space between columns.
sectionInset—This property sets or returns the margins for the
sections. It is a structure of type UIEdgeInsets with the properties top,
bottom, left, and right.

itemSize—This property sets or returns the size of the cells. It is a


value of type CGSize.
estimatedItemSize—This property sets or returns the approximate the minimum space between cells in a section. The
size of dynamic sized cells. Declaring an estimated size can help the minimumInteritemSpacingForSectionAt argument is an integer with
Collection View calculate the size of the cells when it is determined by the section’s index.
the size of their content. It is a value of type CGSize.
There are different ways to determine the size of the cells. By default, the
The Flow layout can also designate a delegate to specify a custom size is set by the Collection View. The Size Inspector panel includes options
configuration for each cell. The methods are defined by the to set these values.
UICollectionViewDelegateFlowLayout protocol. The following are the most
frequently used. Figure 11-7: Cell size

collectionView(UICollectionView, layout:
UICollectionViewLayout, sizeForItemAt: IndexPath)—This
method is called by the layout object to get the size of a cell. It must
return a CGSize value with the size of the cell at the location indicated
by the sizeForItemAt argument.
collectionView(UICollectionView, layout: 
UICollectionViewLayout, insetForSectionAt: Int)—This method
Below the Cell Size option there is an option called Estimate Size (Figure
is called by the layout object to get the UIEdgeInsets structure with the
11-7, number 1). If the value of this option is Automatic or Custom, the
inset values for the section (the margins). The insetForSectionAt
cells are display with the size determined in this panel, but we can set the
argument is an integer with the section’s index.
size from the layout by selecting the option None. When the size of the
collectionView(UICollectionView, layout: cells is not determined by the Collection View, we can set it from the layout
UICollectionViewLayout, minimumLineSpacingForSectionAt: object, as shown next.
Int)—This method is called by the layout object to get the CGFloat
value that determines the minimum space between lines in a section. Listing 11-4: Configuring Flow layout
The minimumLineSpacingForSectionAt argument is an integer with 

the section’s index. override func viewDidLoad() {


super.viewDidLoad()
collectionView(UICollectionView, layout: let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: 100, height: 100)
UICollectionViewLayout, layout.sectionInset = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20)
minimumInteritemSpacingForSectionAt: Int)—This method is prepareDataSource()
prepareSnapshot()
called by the layout object to get the CGFloat value that determines }

method. From this method,


collectionView(UICollectionView, layout:, sizeForItemAt:)
The layout object is stored in the collectionViewLayout property. This property we can return the size of each cell, as shown next.
returns a generic object of type UICollectionViewLayout that we need to cast to
our subclass (in this case, UICollectionViewFlowLayout). The code in Listing 11-4 Listing 11-5: Declaring a specific size for each cell
shows the changes we must introduce to the viewDidLoad() method of our 
example to get the layout object and modify its properties. In this case, we import UIKit
assign a CGSize value to the itemSize property to give the cells a size of
class MyCollectionViewController: UICollectionViewController,
100x100 points and add a padding around them with the sectionInset UICollectionViewDelegateFlowLayout {
property. override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
Figure 11-8: Cells with custom size and margins prepareDataSource()
prepareSnapshot()
}
func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
indexPath, itemID in
if let item = AppData.items.first(where: { $0.id == itemID }) {
cell.picture.image = UIImage(named: item.image)
}
}
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
(collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
 itemID)
}
}
Do It Yourself: Update the viewDidLoad() method in the func prepareSnapshot() {
class with the code in Listing 11-4. Select the
MyCollectionViewController var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
Collection View in the Storyboard, open the Size Inspector panel, snapshot.appendItems(AppData.items.map({ $0.id }))
and select the None option for the Estimate Size parameter (Figure AppData.dataSource.apply(snapshot)
}
11-7, number 1). Run the application. You should see something like func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout:
Figure 11-8. UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var width: CGFloat = 146
var height: CGFloat = 100
The value of the itemSize property implemented in the previous example is
if indexPath.item % 3 == 0 {
assigned to every cell in the Collection View. If we want to assign a width = 292
different size for each cell, we can conform to the height = 200
UICollectionViewDelegateFlowLayout protocol and implement the }
return CGSize(width: width, height: height)
}
}

The layout object calls the protocol methods on the delegate assigned to
the Collection View’s delegate property, so we must designate our view
controller as the delegate for these methods to be called. The Flow layout
calls the collectionView(UICollectionView, layout:, sizeForItemAt:) method every time
it needs to know the size of a cell to calculate the layout. In this example,
we decided to assign a size of 146x100 points to every cell, except when
the index of the cell is multiple of 3. When the remainder of the division of
the index by 3 is equal to 0, we assign a size of 292x200 points. As a result,
every three items, the cell is displayed in a larger size.

Figure 11-9: Cells of different size

Do It Yourself: Update the MyCollectionViewController class with the


code in Listing 11-5 and run the application. You should see
something like Figure 11-9.

Custom Layout

With Flow layout, we can achieve many designs, but the number of cells
displayed in a row or column is always determined by the layout according
to the space available. Flow layout gets the dimensions of the Collection
View, subtracts the margins and the space between cells, and then
positions the cells that fit into the remaining space. If, for example, we set
the minimumInteritemSpacing property to a value too big to fit two cells in the
same row, Flow layout will show only one cell and move the second cell to 
a new row. The only way to make sure that the cells are organized the way
we want, is to define a custom layout object. The items are grouped horizontally or vertically, the groups repeat over
Although we can create our own subclass of the UICollectionViewLayout class and over again to show all the items available, and one or multiple sections
to define a custom layout, the framework includes a more flexible can be used to present these groups. Each part is highly configurable and
alternative called Compositional Layouts. Compositional layouts are flexible, starting with the layout itself. The following are the initializers
defined by the UICollectionViewCompositionalLayout class and their main included in the UICollectionViewCompositionalLayout class to create the layout.
characteristic is that they can organize (compose) a layout from smaller
parts. There are three main parts in a compositional layout: the item (a UICollectionViewCompositionalLayout(section:
cell), the group (a group of cells), and the section (a subdivision of the NSCollectionLayoutSection)—This initializer creates a
layout that can include one or multiple groups). Figure 11-10, below, compositional layout with the section specified by the argument.
illustrates these parts in a simple layout.
UICollectionViewCompositionalLayout(section:
Figure 11-10: Compositional layout
NSCollectionLayoutSection, configuration:
UICollectionViewCompositionalLayoutConfiguration)—This
initializer creates a compositional layout with the section specified by
the section argument and with the configuration specified by the
configuration argument.
UICollectionViewCompositionalLayout(sectionProvider:
Closure)—This initializer creates a compositional layout with the
section returned by the closure specified by the sectionProvider
argument.
UICollectionViewCompositionalLayout(sectionProvider: The NSCollectionLayoutItem class also includes the following properties to
Closure, configuration: configure the items.
UICollectionViewCompositionalLayoutConfiguration)—This
initializer creates a compositional layout with the section returned by contentInsets—This property sets or returns a structure of type
the closure specified by the sectionProvider argument and with the NSDirectionalEdgeInsets with the insets for the content's left, right, top,
and bottom sides. These values are defined from the structure's
configuration specified by the configuration argument.
initializer (NSDirectionalEdgeInsets(top: CGFloat, leading: CGFloat, bottom:
CGFloat, trailing: CGFloat)).
The layout is configured with an object of the
UICollectionViewCompositionalLayoutConfiguration class. The class includes the edgeSpacing—This property sets or returns an object of type
following properties to configure the layout. NSCollectionLayoutEdgeSpacing that defines the space on the sides of the
layout. The space is defined with an object of type
interSectionSpacing—This property sets or returns a CGFloat value NSCollectionLayoutSpacing, which includes the type methods fixed(CGFloat)

that determines the space between sections. for fixed values, and flexible(CGFloat) for flexible values. The values for
each side are declared by the NSCollectionLayoutEdgeSpacing class'
scrollDirection—This property sets or returns a value that
initializer (NSCollectionLayoutEdgeSpacing(leading: NSCollectionLayoutSpacing?,
determines the scrolling orientation. It is an enumeration of type
top: NSCollectionLayoutSpacing?, trailing: NSCollectionLayoutSpacing?, bottom:
ScrollDirection with the values vertical and horizontal.
NSCollectionLayoutSpacing?)).

Once we have the layout, we must define its parts. The items are defined
The items are organized in groups. The framework includes the
by the NSCollectionLayoutItem class. The class includes the following
NSCollectionLayoutGroup class to create them. The class includes the following
initializers.
methods to define and configure a group.
NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize)
—This initializer creates an item of the size specified by the layoutSize horizontal(layoutSize: NSCollectionLayoutSize, subitems:
argument. [NSCollectionLayoutItem])—This method returns a group with the
items in horizontal order. The layoutSize argument determines the
NSCollectionLayoutItem(layoutSize: NSCollectionLayoutSize,
size of the group, and the subitems argument determines the type of
supplementaryItems: items the group is going to contain. The number of items in the group
[NSCollectionLayoutSupplementaryItem])—This initializer is determined by the space available.
creates an item of the size specified by the layoutSize argument for
the supplementary views specified by the supplementaryItems horizontal(layoutSize: NSCollectionLayoutSize, subitem:
argument. NSCollectionLayoutItem, count: Int)—This method returns a
group with the items in horizontal order. The layoutSize argument
determines the size of the group, the subitem argument determines

the type of items the group is going to contain, and the count the group argument.
argument specifies how many.
vertical(layoutSize: NSCollectionLayoutSize, subitems: To configure the section, the NSCollectionLayoutSection class includes the
[NSCollectionLayoutItem])—This method returns a group with the following properties.
items in vertical order. The layoutSize argument determines the size
of the group, and the subitems argument determines the type of contentInsets—This property sets or returns a structure of type
items the group is going to contain. The number of items in the group NSDirectionalEdgeInsets with the inset for the left, right, top and bottom
is determined by the space available. sides of the section. These values are defined from the structure's
initializer (NSDirectionalEdgeInsets(top: CGFloat, leading: CGFloat, bottom:
vertical(layoutSize: NSCollectionLayoutSize, subitem:
CGFloat, trailing: CGFloat)).
NSCollectionLayoutItem, count: Int)—This method returns a
group with the items in vertical order. The layoutSize argument interGroupSpacing—This property sets or returns a CGFloat value
determines the size of the group, the subitem argument determines that determines the space between groups.
the type of items the group is going to contain, and the count orthogonalScrollingBehavior—This property sets or returns a
argument specifies how many. value that determines the section's scrolling behavior. When it is
custom(layoutSize: NSCollectionLayoutSize, itemProvider: declared, the section scrolls perpendicular to the layout. It is an
Closure)—This method returns a group with the size determined by enumeration of type UICollectionLayoutSectionOrthogonalScrollingBehavior
the layoutSize argument and the item returned by the closure with the values continuous, continuousGroupLeadingBoundary, groupPaging,
groupPagingCentered, paging, and none.
assigned to the itemProvider argument.

The NSCollectionLayoutGroup class also includes a property to specify the The size of items and groups is determined by objects of type
NSCollectionLayoutSize. The class includes the following initializer.
space between items.

interItemSpacing—This property sets or returns an object of type NSCollectionLayoutSize(widthDimension:


to define the space between items. The class
NSCollectionLayoutSpacing
NSCollectionLayoutDimension, heightDimension:
includes the methods fixed(CGFloat) for fixed values, and flexible(CGFloat) NSCollectionLayoutDimension)—This initializer creates an
for flexible values. NSCollectionLayoutSize object with the dimensions determined by the
arguments. The arguments are objects of type
Groups are the components of sections and sections are defined with the NSCollectionLayoutDimension. This class includes the type methods

NSCollectionLayoutSection class. The class includes the following initializer. absolute(CGFloat) to provide absolute values, estimated(CGFloat) to provide
a size estimate, fractionalWidth(CGFloat) to calculate the value from the
NSCollectionLayoutSection(group: NSCollectionLayoutGroup) width of the container, and fractionalHeight(CGFloat) to calculate the
—This initializer creates a section with the type of groups specified by
value from the height of the container. The fractional values are }
func createLayout() -> UICollectionViewCompositionalLayout {
specified with numbers from 0.0 to 1.0. let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5),
heightDimension: .fractionalWidth(0.5))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
It is time to see an example of how to compose a complex layout with all item.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 5, bottom: 5, trailing: 10)
these tools. The process is simple, we must create a
UICollectionViewCompositionalLayout object with a definition of the layout's let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
heightDimension: .fractionalWidth(0.5))
items, groups, and sections, and assign it to the Collection View. In the let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
following example, we create this object from a method and then call the
let section = NSCollectionLayoutSection(group: group)
method from viewDidLoad() to create the custom layout and assign it to the
Collection View as soon as the scene is loaded. let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}
Listing 11-6: Assigning a compositional layout to the Collection View }
 

import UIKit
The process to define a compositional layout is declarative. We define the
class MyCollectionViewController: UICollectionViewController { object that configures the items, then the object that configures the
override func viewDidLoad() {
super.viewDidLoad() groups, then the object that configures the sections, and finally the layout
let customLayout = createLayout() object that contains the sections. In the createLayout() method of Listing 11-
collectionView.collectionViewLayout = customLayout
prepareDataSource() 6, we begin by defining the size of the items. To adapt the items to the
prepareSnapshot() space available, we use fractional values. The width dimension was
}
declared as half the width of the container, and we do the same for the
func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell, height. This means that the system is going to get the width of the group,
indexPath, itemID in divide it by half (width * 0.5), and assign the result to the item's width and
if let item = AppData.items.first(where: { $0.id == itemID }) {
cell.picture.image = UIImage(named: item.image) height. For instance, if the screen has a width of 320 points and the group
} is declared of the same size, the items are going to have a size of 160 by
}
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
160 points (320 * 0.5). With this size, we instantiate the
(collectionView: collectionView) { (collection, indexPath, itemID) in NSCollectionLayoutItem object and then assign a new value to the contentInsets
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: property to separate the items from each other and the edges of the
itemID)
} container.
} With the items configured, we proceed to define the group that is going to
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>() contain those items. First, the size of the group is declared with fractional
snapshot.appendSections([.main]) values. This time, we assign a width that is 100% the size of the group's
snapshot.appendItems(AppData.items.map({ $0.id }))
container (fractionalWidth(1)) and a height that is half the width. Again, if the
AppData.dataSource.apply(snapshot)

screen is 320 points wide, the group is going to have a width of 320 points
and a height of 160. Next, we use the horizontal() type method to create the Listing 11-7: Customizing a compositional layout
group using the size we just calculated and an array with the 
NSCollectionLayoutItem object defined before. This asks the layout to align the func createLayout() -> UICollectionViewLayout {
items in the group horizontally and fit as many as possible. Finally, the let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.2),
heightDimension: .fractionalWidth(0.2))
section is defined with this group and the layout is defined with that let item = NSCollectionLayoutItem(layoutSize: itemSize)
section. item.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 5, bottom: 5, trailing: 10)

This example creates a simple layout, like the one built with a Flow layout let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension:
before, but with the difference that the size of the items is determined by .fractionalWidth(0.2))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
the space available. The layout organizes the items in one section and one
group, and because the size of each item was specified as half the width of let section = NSCollectionLayoutSection(group: group)
the container, only two are included per group, no matter the space
let layout = UICollectionViewCompositionalLayout(section: section)
available. return layout
}

Figure 11-11: Compositional layout
In this example, the width and height of the items were declared as 20%
the width of the container, which means that a total of five items will fit in
a group. Notice that the height of the group was defined as 20% of the
width of the container, so it matches the size of the items.

Figure 11-12: Compositional layout with five columns

Do It Yourself: Update the MyCollectionViewController class with the


code in Listing 11-6. Run the application and rotate the device. You
should see the cells adapting to the space available, as illustrated in
Figure 11-11.

Designing the perfect layout for our application is easy with a
compositional layout. For instance, we can include five columns of items by
assigning a fractional size of 0.2 to the items.
Supplementary Views Once the prototype view is created, we must create the supplementary
views from it and provide them to the Collection View. The

UICollectionViewDiffableDataSource includes the following property for this
purpose.
Besides cells, Collection Views use other views to include additional
content such as headers and footers. These views are called Supplementary
supplementaryViewProvider—This property sets a closure that
Views and are managed by the layout object. For instance, the Flow layout
the Collection View executes to get the supplementary views (e.g.,
includes two supplementary views, one for the header and another for the
footer of every section. headers and footers). The closure receives three values: a reference to
As well as cells, supplementary views are reusable; they are created from the Collection View, a string that describes the type of view required,
prototype views. The UICollectionView class defines the SupplementaryRegistration and the index path with the location of the view.
structure to register these views. The structure includes the following
initializer. The closure assigned to the supplementaryViewProvider property must create
the supplementary view from a prototype and return it. The UICollectionView
SupplementaryRegistration(elementKind: String, handler: class includes the following method for this purpose.
Closure)—This initializer registers and configures a prototype view.
The elementKind argument is an identifier that determines the type dequeueConfiguredReusableSupplementary (using:
of view to create (header, footer, etc.), and the handler argument is a SupplementaryRegistration, for: IndexPath)—This method
closure that is called every time a supplementary view is required. returns a supplementary view with the configuration defined by the
SupplementaryRegistration structure specified by the using argument. The
The closure receives three values: a reference to the view, the view's
identifier, and the index path with the location. for argument indicates the view's location.

Although we can use whatever value we want to identify the view, the The SupplementaryRegistration structure is generic, which means it needs to
UICollectionView class includes the following type properties with predefined
know the data type of the view is going to use to create the prototype
identifiers for headers and footers. views. UIKit defines the UICollectionReusableView class to create these views.
The process is the same we have used before for custom cells in Table
elementKindSectionHeader—This property returns a string to Views. We must define the properties to reference the elements in the
identify a header. view and then create those elements when the view is initialized. The
following example defines a reusable view with a background image and a
elementKindSectionFooter—This property returns a string to label at the center.
identify a footer.
Listing 11-8: Defining a reusable view

import UIKit AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>


(collectionView: collectionView) { (collection, indexPath, itemID) in
class MyHeader: UICollectionReusableView { return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
var pictureView = UIImageView() itemID)
var textView = UILabel() }
let headerRegistration = UICollectionView.SupplementaryRegistration<MyHeader>
override init(frame: CGRect) { (elementKind: UICollectionView.elementKindSectionHeader) { headerView, kind, indexPath in
super.init(frame: frame) headerView.pictureView.image = UIImage(named: "gradientTop")
pictureView.translatesAutoresizingMaskIntoConstraints = false headerView.textView.text = "My Food"
pictureView.contentMode = .scaleToFill }
self.addSubview(pictureView) AppData.dataSource.supplementaryViewProvider = { view, kind, indexPath in
return self.collectionView.dequeueConfiguredReusableSupplementary(using:
pictureView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true headerRegistration, for: indexPath)
pictureView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true }
pictureView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true }
pictureView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true 

textView.translatesAutoresizingMaskIntoConstraints = false
textView.font = UIFont.preferredFont(forTextStyle: .title1) After the cells are defined as always, we create a SupplementaryRegistration
self.addSubview(textView) structure using our view's data type (MyHeader). In the closure, we initialize
textView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
the properties of the view with a background image called gradientTop
textView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true (available on our website) and the title "My Food".
} Creating supplementary views from this prototype is simple. We just have
required init?(coder: NSCoder) {
fatalError("Error") to assign a closure to the supplementaryViewProvider property of the diffable
} data source, call the dequeueConfiguredReusableSupplementary() method with the
}

registration structure, and return the view produced by it, as we did for
cells before.
Once we have our view class, we can register the prototype and create the This creates the views, but they are not included in the Collection View.
supplementary views. The following are the changes we need to introduce This is the responsibility of the layout object. For instance, Compositional
to our prepareDataSource() method. layout works with NSCollectionLayoutBoundarySupplementaryItem objects to
define headers and footers. The class includes the following initializer.
Listing 11-9: Defining the supplementary views
 NSCollectionLayoutBoundarySupplementaryItem(layoutSize:
func prepareDataSource() { NSCollectionLayoutSize, elementKind: String, alignment:
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
indexPath, itemID in
NSRectAlignment)—This initializer creates a layout item called
if let item = AppData.items.first(where: { $0.id == itemID }) { boundary supplementary item that is used to add headers and footers
cell.picture.image = UIImage(named: item.image)
}
to a section. The layoutSize argument determines the size of the view,
} the elementKind argument is the identifier we used to create the
view, and the alignment argument determines the position of the 

view in relation to the section. It is an enumeration with the values


In this example, we define a NSCollectionLayoutSize object with a flexible
top, topLeading, leading, bottomLeading, bottom, bottomTrailing, trailing,
width and a height of 45 points. The boundary supplementary item is
topTrailing, and none.
created next with this value and the "Header" identifier (this is the same
identifier we used to create the view before). Notice that the layout
Once the boundary supplementary item is created, we must assign it to the doesn't know whether we are adding a header or a footer, so we specify
section. The NSCollectionLayoutSection class includes the following property for the top value for the alignment argument to show the view at the top of
this purpose. the section. Finally, the item is assigned to the section and shown on the
screen.
boundarySupplementaryItems—This property sets or returns an
array of NSCollectionLayoutBoundarySupplementaryItem objects that Figure 11-13: Collection View header
represent the supplementary items associated with the section.

The following are the changes we need to introduce to the compositional


layout of our example to add the header to the Collection View.

Listing 11-10: Adding supplementary views to a compositional layout



func createLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension:
.fractionalWidth(0.5))
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 5, bottom: 5, trailing: 10)

let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension:


.fractionalWidth(0.5))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

let section = NSCollectionLayoutSection(group: group)


let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(60))
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize:
headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
section.boundarySupplementaryItems = [sectionHeader]

let layout = UICollectionViewCompositionalLayout(section: section)
return layout
}

Do It Yourself: Create a file with a subclass of UICollectionReusableView Sections


called MyHeader. Complete the class with the code in Listing 11-8.

Update the prepareDataSource() method in the MyCollectionViewController
class with the code in Listing 11-9 and the createLayout() method with Collection Views may organize the information in sections. The sections
the code in Listing 11-10. Download the image gradientTop from our must be defined in the model and then the snapshot must be created from
website and add it to the Assets Catalog. Run the application. You these values, as we did with Table Views. There are just a few changes we
should see the header of Figure 11-13. must introduce in our example to work with more than one section. For
the model, we can use the one defined in Chapter 10 for Table Views (see
Listing 10-36). As always, we must define the dataSource property with the
UICollectionViewDiffableDataSource class, but the rest of the code remains the
same.
In the view controller, the changes are minor. The diffable data source now
has to work with section IDs (Section.ID), and the supplementary view for
the header must display the section's name instead of a predefined string,
as shown next.

Listing 11-11: Working with multiple sections



func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
indexPath, itemID in
if let item = AppData.items.first(where: { $0.id == itemID }) {
cell.picture.image = UIImage(named: item.image)
}
}
AppData.dataSource = UICollectionViewDiffableDataSource<Sections.ID, ItemsData.ID>
(collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
itemID)
}
let headerRegistration = UICollectionView.SupplementaryRegistration<MyHeader>(elementKind:
UICollectionView.elementKindSectionHeader) { headerView, kind, indexPath in
headerView.pictureView.image = UIImage(named: "gradientTop")
headerView.textView.text = AppData.sections[indexPath.section].name
}
AppData.dataSource.supplementaryViewProvider = { view, kind, indexPath in
return self.collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration,
for: indexPath)
}
}

The snapshot is the same we used for Table Views. The following is the
prepareSnapshot() method for this example.

Listing 11-12: Snapshot for multiple sections



func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections.ID, ItemsData.ID>()
snapshot.appendSections(AppData.sections.map({ $0.id }))
for section in AppData.sections {
let itemIDs = AppData.items.compactMap({ value in
return value.section == section.name ? value.id : nil
})
snapshot.appendItems(itemIDs, toSection: section.id)
}
AppData.dataSource.apply(snapshot)
}

Because the SupplementaryRegistration structure defines the content of the


header views, no changes are required for the layout and therefore the
rest of the code remains the same. The result is shown below.

Figure 11-14: Sections with headers
Do It Yourself: Update the ApplicationData.swift file with the
model in Chapter 10, Listing 10-36. Define the dataSource property in
the model with the UICollectionViewDiffableDataSource class (var dataSource:
UICollectionViewDiffableDataSource<Sections.ID, ItemsData.ID>!). Update the
prepareDataSource() and prepareSnapshot() methods in your view controller
with the codes in Listing 11-11 and 11-12. Run the application. You
should see something like Figure 11-14.

The information included in the sections of our example belong to the As we will see in the next section of this chapter, section snapshots are
same topic. In this case, all items represent food. But Collection Views can frequently used to create hierarchical lists, but they are also useful when
work with information provided by multiple sources or with different the information of each section is unique. For example, the following
characteristics. For instance, a Collection View may include two sections, model defines two sections, one for the items selected by the user and
one to show the items available and another to display those selected by another for the items that are still available to select.
the user. In cases like this, it may be useful to create a snapshot for each
section. The framework provides the NSDiffableDataSourceSectionSnapshot class Listing 11-13: Defining a model to test section snapshots
for this purpose. As a normal snapshot, the class includes all the properties 
and methods required to add and remove items, but also some that are import UIKit
specific to section snapshots. The following are the most frequently used.
enum Sections {
case selected, available
items—This property returns an array with the identifiers of the }
class ItemsData: Identifiable {
items in the section. var id: UUID = UUID()
var name: String
rootItems—This property returns an array with the identifiers of the var image: String
items at the top of the snapshot's hierarchy. var calories: Int
var selected: Bool
append([ItemIdentifierType], to: ItemIdentifierType?)—This
init(_ name: String, _ image: String, _ calories: Int, _ selected: Bool) {
method adds the items specified by the first argument to the section self.name = name
specified by the to argument. self.image = image
self.calories = calories
contains(ItemIdentifierType)—This method returns a Boolean self.selected = selected
}
value that determines whether the item specified by the argument is }
included in the section. struct ApplicationData {
var dataSource: UICollectionViewDiffableDataSource<Sections, ItemsData.ID>!
expand([ItemIdentifierType])—This method expands the section var items: [ItemsData] = [] {
to show the items specified by the argument. didSet {
items.sort(by: { $0.name < $1.name })
collapse([ItemIdentifierType])—This method collapses the section }
}
to hide the items specified by the argument. init() {
items.append(contentsOf: [ItemsData("Bagels", "bagels", 250, false), ItemsData("Brownies",
isExpanded(ItemIdentifierType)—This method returns a Boolean "brownies", 466, false), ItemsData("Butter", "butter", 717, false), ItemsData("Cheese", "cheese", 402,
value to indicate whether the item specified by the argument is in the false), ItemsData("Coffee", "coffee", 0, false), ItemsData("Cookies", "cookies", 502, false),
ItemsData("Donuts", "donuts", 452, false), ItemsData("Granola", "granola", 471, false),
expanded state. ItemsData("Juice", "juice", 23, false), ItemsData("Lemonade", "lemonade", 40, false),
ItemsData("Lettuce", "lettuce", 15, false), ItemsData("Milk", "milk", 42, false),
ItemsData("Oatmeal", "oatmeal", 68, false), ItemsData("Potatoes", "potato", 77, false),
ItemsData("Tomatoes", "tomato", 18, false), ItemsData("Yogurt", "yogurt", 59, false)])
} }
} AppData.dataSource.supplementaryViewProvider = { view, kind, indexPath in
var AppData = ApplicationData() return self.collectionView.dequeueConfiguredReusableSupplementary(using:
 headerRegistration, for: indexPath)
}
}
The sections are identified by the Sections enumeration. The enumeration func prepareSnapshot() {
includes two values: selected, for the selected items, and available, for the var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.selected, .available])
items available to select. The diffable data source is defined with this data AppData.dataSource.apply(snapshot, animatingDifferences: true)
type, but the rest of the code in the model is the same as before.
In the view controller, we need to create two section snapshots and then let selectedIDs = AppData.items.compactMap({ value in
return value.selected ? value.id : nil
update their content when an item is selected by the user, as shown next. })
var selectedSnapshot = NSDiffableDataSourceSectionSnapshot<ItemsData.ID>()
selectedSnapshot.append(selectedIDs)
Listing 11-14: Moving items between section snapshots AppData.dataSource.apply(selectedSnapshot, to: .selected, animatingDifferences: false)

let availableIDs = AppData.items.compactMap({ value in
import UIKit
return value.selected ? nil : value.id
})
class MyCollectionViewController: UICollectionViewController {
var availableSnapshot = NSDiffableDataSourceSectionSnapshot<ItemsData.ID>()
override func viewDidLoad() {
availableSnapshot.append(availableIDs)
super.viewDidLoad()
AppData.dataSource.apply(availableSnapshot, to: .available, animatingDifferences: false)
let customLayout = createLayout()
}
collectionView.collectionViewLayout = customLayout
func createLayout() -> UICollectionViewLayout {
prepareDataSource()
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.33),
prepareSnapshot()
heightDimension: .fractionalWidth(0.33))
}
let item = NSCollectionLayoutItem(layoutSize: itemSize)
func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1),
indexPath, itemID in
heightDimension: .fractionalWidth(0.33))
if let item = AppData.items.first(where: { $0.id == itemID }) {
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
cell.picture.image = UIImage(named: item.image)
}
let section = NSCollectionLayoutSection(group: group)
}
let headerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
heightDimension: .absolute(60))
(collectionView: collectionView) { (collection, indexPath, itemID) in
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(layoutSize: headerSize,
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
elementKind: UICollectionView.elementKindSectionHeader, alignment: .top)
itemID)
section.boundarySupplementaryItems = [sectionHeader]
}
let headerRegistration = UICollectionView.SupplementaryRegistration<MyHeader>
let layout = UICollectionViewCompositionalLayout(section: section)
(elementKind: UICollectionView.elementKindSectionHeader) { headerView, kind, indexPath in
return layout
if let sectionID = AppData.dataSource.sectionIdentifier(for: indexPath.section) {
}
headerView.pictureView.image = UIImage(named: "gradientTop")
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath:
headerView.textView.text = sectionID == .selected ? "Selected" : "Available"
IndexPath) {
}
if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) {

if let item = AppData.items.first(where: { $0.id == itemID }) { UICollectionViewDelegateprotocol, which is called every time the user taps on a
if item.selected {
var selectedSnapshot = AppData.dataSource.snapshot(for: .selected) cell. In this method, we perform two operations, depending on whether
selectedSnapshot.delete([itemID]) the item was previously selected (the value of the item's selected property is
AppData.dataSource.apply(selectedSnapshot, to: .selected, animatingDifferences: true)
true). First, we delete the item from the current section and then create a
item.selected = false
new snapshot to update the other section (If the item is selected, we
let availableIDs = AppData.items.compactMap({ value in remove it from the selected section and update the available section, or vice
return value.selected ? nil : value.id
}) versa).
var availableSnapshot = NSDiffableDataSourceSectionSnapshot<ItemsData.ID>() Now we have two sections that manage different information.
availableSnapshot.append(availableIDs)
AppData.dataSource.apply(availableSnapshot, to: .available, animatingDifferences: false)
} else { Figure 11-15: Section snapshots
var availableSnapshot = AppData.dataSource.snapshot(for: .available)
availableSnapshot.delete([itemID])
AppData.dataSource.apply(availableSnapshot, to: .available, animatingDifferences: true)
item.selected = true

let selectedIDs = AppData.items.compactMap({ value in


return value.selected ? value.id : nil
})
var selectedSnapshot = NSDiffableDataSourceSectionSnapshot<ItemsData.ID>()
selectedSnapshot.append(selectedIDs)
AppData.dataSource.apply(selectedSnapshot, to: .selected, animatingDifferences: false)
}
}
}
} 
}

Do It Yourself: Update the ApplicationData.swift file with the code
There are only a few changes in this view controller from the previous one. in Listing 11-13 and the MyCollectionViewController class with the code in
First, we modify the diffable data source to work with Sections values. Listing 11-14. Run the application and select an item. You should see
Another small change is in the registration of the header view. Depending the item moving to the Selected section.
on the section identifier, we display the string "Selected" or "Available".
The big changes are in the prepareSnapshot() method. A normal snapshot is
defined first to manage the two sections. After this snapshot is applied, we
filter the items and create two section snapshots, one with the list of
selected items and another with the unselected items.
To let the user select or deselect an item, we implement the
collectionView(UICollectionView, didSelectItemAt:) method defined by the
11.3 Lists accessories—This property sets or returns an array of UICellAccessory
structures that represent the accessories to show in the cell.

indentationLevel—This property sets or returns an integer value
Collection Views can also display the items as a one-column list, in a format that determines the level of indentation for the cell.
that emulates Table Views. The difference with Table Views is that indentationWidth—This property sets or returns a CGFloat value
Collection View Lists are more versatile and better optimized. In fact, Apple with the width of an indentation level.
recommends implementing a Collection View List instead of a Table View
when possible. The UICollectionViewCompositionalLayout class includes the indentsAccessories—This property sets or returns a Boolean value
following type method to produce a compositional layout that organizes that determines whether the cell indents accessories on the leading
the views in a list. edge.

list(using: UICollectionLayoutListConfiguration)—This type To turn a Collection View into a list, we must get the List Layout with the
method creates a compositional layout that displays the views on a list() method, assign it to the Collection View, register a
UICollectionViewListCell, and configure the cell as we did before for Table
list. The using argument is a structure that defines the style for the
Views.
list.
Listing 11-15: Creating a Collection View List
Like Table Views, a Collection View List can have different styles or

appearances. This configuration is determined by a
import UIKit
UICollectionLayoutListConfiguration structure.
class MyCollectionViewController: UICollectionViewController {
override func viewDidLoad() {
UICollectionLayoutListConfiguration(appearance: super.viewDidLoad()
Appearance)—This initializer creates a configuration structure to let config = UICollectionLayoutListConfiguration(appearance: .grouped)
let layout = UICollectionViewCompositionalLayout.list(using: config)
define the appearance of the list. The appearance argument is an collectionView.collectionViewLayout = layout
enumeration with the values plain, grouped, insetGrouped, sidebar, and
prepareDataSource()
sidebarPlain. prepareSnapshot()
}
func prepareDataSource() {
To create the cells, the framework includes the UICollectionViewListCell class. let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell,
The class implements the defaultContentConfiguration() method to configure the ItemsData.ID> { cell, indexPath, itemID in
content and the following properties for the accessories and layout. if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
config.text = item.name
config.secondaryText = "Calories: \(item.calories)"
config.image = UIImage(named: item.image)

config.imageProperties.maximumSize = CGSize(width: 60, height: 60)


cell.contentConfiguration = config
}
}
AppData.dataSource = UICollectionViewDiffableDataSource<Sections.ID, ItemsData.ID>
(collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
itemID)
}
}
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections.ID, ItemsData.ID>()
snapshot.appendSections(AppData.sections.map({ $0.id }))
for section in AppData.sections {
let itemIDs = AppData.items.compactMap({ value in
return value.section == section.name ? value.id : nil
})
snapshot.appendItems(itemIDs, toSection: section.id)
}
AppData.dataSource.apply(snapshot)
}
}

This view controller creates a configuration structure with the grouped


appearance, gets the layout with this value, and assigns it to the Collection
View. The rest of the code is very similar to previous examples, especially
those designed for Table Views. The result is shown below.

Figure 11-16: Collection View List
Do It Yourself: Create a new project. Replace the initial scene with
a Collection View Controller. Create a subclass of
UICollectionViewController called MyCollectionViewController and assign it to
the scene. Update the class with the code in Listing 11-15. Create a
Swift file called ApplicationData.swift for the model in Chapter 10,
Listing 10-36. Remember to update the dataSource property to work
with the diffable data source for Collection Views (var dataSource:
UICollectionViewDiffableDataSource<Sections.ID, ItemsData.ID>!). Add the
thumbnails to the Assets Catalog. Run the application. You should required init?(coder: NSCoder) {
fatalError("Error")
see the interface in Figure 11-16. }
}

Headers and footers are still added to the Collection View by the diffable
data source, but now the layout configuration must specify the type of
Although we must explicitly declare in the layout configuration structure
header or footer we want to use. The UICollectionLayoutListConfiguration class
that we are working with supplementary views, the process for registering
includes the following properties for this purpose.
and creating these views remain the same.

headerMode—This property sets or returns an enumeration value Listing 11-17: Adding supplementary views to the list
that determines the type of header to use for the list. The values 
available are none, supplementary, and firstItemInSection. import UIKit
footerMode—This property sets or returns an enumeration value class MyCollectionViewController: UICollectionViewController {
that determines the type of footer to use for the list. The values override func viewDidLoad() {
super.viewDidLoad()
available are none and supplementary. var config = UICollectionLayoutListConfiguration(appearance: .grouped)
config.headerMode = .supplementary
let layout = UICollectionViewCompositionalLayout.list(using: config)
As always, we need a UICollectionReusableView subclass to design the header. collectionView.collectionViewLayout = layout
The following example includes a label with a headline style at the center of
the view. prepareDataSource()
prepareSnapshot()
}
Listing 11-16: Defining the header for a list func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell,
 ItemsData.ID> { cell, indexPath, itemID in
import UIKit if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
class MyHeader: UICollectionReusableView { config.text = item.name
var textView = UILabel() config.secondaryText = "Calories: \(item.calories)"
config.image = UIImage(named: item.image)
override init(frame: CGRect) { config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
super.init(frame: frame) cell.contentConfiguration = config
textView.translatesAutoresizingMaskIntoConstraints = false }
textView.font = UIFont.preferredFont(forTextStyle: .headline) }
self.addSubview(textView) AppData.dataSource = UICollectionViewDiffableDataSource<Sections.ID, ItemsData.ID>
(collectionView: collectionView) { (collection, indexPath, itemID) in
textView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
textView.topAnchor.constraint(equalTo: self.topAnchor, constant: 16).isActive = true itemID)
textView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -16).isActive = true }
}

let headerRegistration = UICollectionView.SupplementaryRegistration<MyHeader> itemSeparatorHandler—This property sets or returns a closure


(elementKind: UICollectionView.elementKindSectionHeader) { headerView, kind, indexPath in
headerView.textView.text = AppData.sections[indexPath.section].name that is executed to get the configuration for the separator of each
} item.
AppData.dataSource.supplementaryViewProvider = { view, kind, indexPath in
return self.collectionView.dequeueConfiguredReusableSupplementary(using:
headerRegistration, for: indexPath) The configuration is defined by the UIListSeparatorConfiguration structure. The
}
} following are some of its properties.
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections.ID, ItemsData.ID>()
snapshot.appendSections(AppData.sections.map({ $0.id }))
color—This property sets or returns a UIColor value with the
for section in AppData.sections { separator's color.
let itemIDs = AppData.items.compactMap({ value in
return value.section == section.name ? value.id : nil topSeparatorVisibility—This property sets or returns a value that
})
determines whether the separator at the top of the cell is visible or
snapshot.appendItems(itemIDs, toSection: section.id)
} not. It is a Visibility enumeration with the values automatic, hidden, and
AppData.dataSource.apply(snapshot)
visible.
}
} bottomSeparatorVisibility—This property sets or returns a value

that determines whether the separator at the bottom of the cell is
Do It Yourself: Create a file with a subclass of UICollectionReusableView visible or not. It is a Visibility enumeration with the values automatic,
called MyHeader. Complete the class with the code in Listing 11-16. hidden, and visible.

Update the MyCollectionViewController class with the code in Listing 11- topSeparatorInsets—This property sets or returns an
17. Run the application. You should see the headers with the title of value that defines the padding around the
NSDirectionalEdgeInsets
each section. separator at the top of the cell.
bottomSeparatorInsets—This property sets or returns an
Another aspect of the list we can configure are the separators (the lines value that defines the padding around the
NSDirectionalEdgeInsets
between cells). The UICollectionLayoutListConfiguration structure includes the
separator at the bottom of the cell.
following properties for this purpose.
A list displays lines between the cells, but also at the top and bottom of
showsSeparators—This property sets or returns a Boolean value
each section. We can modify this behavior from the closure assigned to the
that determines whether the separators are displayed or not. itemSeparatorHandler property. In the following example, we change the color
separatorConfiguration—This property sets or returns a value that of the separators to red and then remove the separators at the top and
defines the configuration for the separators. It is a structure of type bottom of each section.
UIListSeparatorConfiguration.
Listing 11-18: Configuring cell's separators

override func viewDidLoad() {
super.viewDidLoad()
var config = UICollectionLayoutListConfiguration(appearance: .grouped)
config.headerMode = .supplementary
config.separatorConfiguration.color = .systemRed

config.itemSeparatorHandler = { indexPath, config in


let row = indexPath.item
let section = indexPath.section

var lastRow = 0
if let sectionID = AppData.dataSource.sectionIdentifier(for: section) {
lastRow = AppData.dataSource.snapshot().numberOfItems(inSection: sectionID)
lastRow = lastRow > 0 ? lastRow - 1 : 0
}
var configuration = config
configuration.topSeparatorVisibility = row == 0 ? .hidden : .automatic
configuration.bottomSeparatorVisibility = row == lastRow ? .hidden : .automatic
return configuration
}
let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView.collectionViewLayout = layout

prepareDataSource() 
prepareSnapshot()
}
As Table Views, Collection View Lists can also include accessories. These

are small icons on the left or right side of the cell that incorporate
The code in Listing 11-18 assigns a closure to the itemSeparatorHandler additional functionality. The accessories are defined by the UICellAccessory
property to configure the separators cell by cell. To know if the current cell structure. The following are some of its methods.
is the first one in the section, we just read the index and hide the top
separator when it is equal to 0 (The first row is always at the index 0), but disclosureIndicator(displayed: DisplayedState, options:
to know whether the cell is the last one in the section, we get the number DisclosureIndicatorOptions)—This method returns an accessory
of items in the section minus 1 and compare that value with the index of that indicates that there is information to disclose. The displayed
the current cell. If they match, it means that the cell is the last one in the argument indicates when the accessory is displayed, and the options
section, so we hide the bottom separator. argument defines the accessory's configuration.
checkmark(displayed: DisplayedState, options:
Figure 11-17: Custom separators
CheckmarkOptions)—This method returns an accessory that

presents a checkmark. The displayed argument indicates when the config.image = UIImage(named: item.image)
config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
accessory is displayed, and the options argument defines the cell.contentConfiguration = config
accessory's configuration.
let selected = item.selected
delete(displayed: DisplayedState, options: DeleteOptions, cell.accessories = selected ? [.checkmark()] : []
}
actionHandler: Closure)—This method returns an accessory that }
allows the user to delete a cell. The displayed argument indicates 

when the accessory is displayed, the options argument defines the


The code in Listing 11-19 defines a new registration for our cell. The cell is
accessory's configuration, and the actionHandler is the closure to be
configured as before, but now we get the value of the item's selected
executed after the user performs the action.
property and assign to the cell a checkmark accessory or an empty array
reorder(displayed: DisplayedState, options: ReorderOptions) depending on the property's value.
—This method returns an accessory that allows the user to reorder To let the user select or deselect a cell, we can implement the
the cells. The displayed argument indicates when the accessory is collectionView(UICollectionView, didSelectItemAt:) method again.
displayed, and the options argument defines the accessory's
configuration. Listing 11-20: Selecting and deselecting a cell in a list

All these accessories use similar values for configuration. For instance, a override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath:
IndexPath) {
value of the DisplayedState enumeration determines when the accessory is if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) {
going to be displayed. The values available are always, whenEditing, and if let item = AppData.items.first(where: { $0.id == itemID }) {
item.selected.toggle()
whenNotEditing. The options for each accessory are defined by specific
structures, but they usually share the same properties, like isHidden to hide var current = AppData.dataSource.snapshot()
the accessory, or tintColor to change its color. Although we can modify any current.reconfigureItems([itemID])
AppData.dataSource.apply(current)
of these values, usually those assigned by default are more than enough.
For instance, in the following example we add an accessory of type collectionView.deselectItem(at: indexPath, animated: true)
}
checkmark to the cells when they are selected or remove it when not. }
}

Listing 11-19: Adding accessories to the cells

Figure 11-18: Checkmark accessories
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, ItemsData.ID>
{ cell, indexPath, itemID in
if let item = AppData.items.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
config.text = item.name
config.secondaryText = "Calories: \(item.calories)"
trailingSwipeActionsConfigurationProvider—This property
takes a closure to configure swipe actions for the cell's trailing edge.
The closure receives an IndexPath value with the location of the cell and
must return a UISwipeActionsConfiguration object with the buttons we
want to show.

The process to add these buttons to the cell is the same we used before for
Table Views. The buttons are defined with the UIContextualAction class, and
then the UISwipeActionsConfiguration object is initialized with these values and
returned. The following example adds a trailing button to delete the cell.

Listing 11-21: Defining the buttons for swipe gestures



override func viewDidLoad() {
super.viewDidLoad()
var config = UICollectionLayoutListConfiguration(appearance: .grouped)
config.headerMode = .supplementary

config.trailingSwipeActionsConfigurationProvider = { indexPath in
let button = UIContextualAction(style: .normal, title: "Remove", handler: { (action, view,

completion) in
if let itemID = AppData.dataSource.itemIdentifier(for: indexPath), let sectionID =
Collection View Lists can also implement functionality to allow users to AppData.dataSource.sectionIdentifier(for: indexPath.section) {
AppData.items.removeAll(where: { $0.id == itemID })
delete cells. The system works in the same way as with Table Views. The
UICollectionView class includes the isEditing property to activate or deactivate var currentSnapshot = AppData.dataSource.snapshot()
currentSnapshot.deleteItems([itemID])
the editing mode (true or false), and the UICollectionLayoutListConfiguration if currentSnapshot.numberOfItems(inSection: sectionID) <= 0 {
structure includes the following properties to provide the actions for the AppData.sections.removeAll(where: { $0.id == sectionID })
swipe gesture. currentSnapshot.deleteSections([sectionID])
}
AppData.dataSource.apply(currentSnapshot)
leadingSwipeActionsConfigurationProvider—This property }
completion(true)
takes a closure to configure swipe actions for the cell's leading edge. })
The closure receives an IndexPath value with the location of the cell and let config = UISwipeActionsConfiguration(actions: [button])
return config
must return a UISwipeActionsConfiguration object with the buttons we }
want to show. let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView.collectionViewLayout = layout

containers for other items, called children, that the user can see by tapping
prepareDataSource()
prepareSnapshot() on the top item's accessory.
} We don't need anything new to create these types of lists, all the

functionality is provided by section snapshots, but the information in the
model must be organized accordingly. For instance, the following model
The closure assigned to the trailingSwipeActionsConfigurationProvider property in
includes a structure to store all the items, but the structure includes a
Listing 11-21 creates a button with an action that removes the cell and the
property with an array of instances of the same structure to store the
section if there are no cells left. The button is used to create the
items that are going to be shown when the parent item is expanded.
UISwipeActionsConfiguration object, and the object is returned to configure the
Listing 11-22: Defining a model for a hierarchical list
list.

import UIKit
Figure 11-19: Swipe button to delete the cells
enum Sections {
case main
}
struct MainItems: Identifiable {
var id = UUID()
var name: String!
var options: [MainItems]!
}
struct ApplicationData {
var dataSource: UICollectionViewDiffableDataSource<Sections, MainItems.ID>!

let items = [
 MainItems(name: "Food", options: [
MainItems(name: "Oatmeal", options: nil),
MainItems(name: "Bagels", options: nil),
Do It Yourself: Replace the viewDidLoad() method in your MainItems(name: "Brownies", options: nil),
MyCollectionViewControllerclass with the method of Listing 11-21. Run MainItems(name: "Cheese", options: nil),
MainItems(name: "Cookies", options: nil),
the application and swipe a cell to the left. Press the Delete button. MainItems(name: "Donuts", options: nil)
The cell should be deleted. If you want to activate the editing mode, ]),
MainItems(name: "Beverages", options: [
as we did for Table Views in Chapter 10, assign the value true to the MainItems(name: "Coffee", options: nil),
isEditing property of the UICollectionView object (collectionView.isEditing = MainItems(name: "Juice", options: nil),
MainItems(name: "Lemonade", options: nil)
true) and add the delete accessory to the cell (cell.accessories = [.delete()]). ])
]
}
Collection View Lists can also work along with section snapshots to create a var AppData = ApplicationData()
hierarchical list. This is a list in which some items can expand or collapse to 
display or hide other items. The main items, also called parents, work as
The MainItems structure includes the name property to store the item's text, }
func prepareSnapshot() {
and the options property to store the children. In this example, we store two var snapshot = NSDiffableDataSourceSectionSnapshot<MainItems.ID>()
parent items called "Food" and "Beverages", and the children for each for mainItem in AppData.items {
snapshot.append([mainItem.id], to: nil)
parent are stored in the item's options property. When the user taps on the
snapshot.append(mainItem.options.map({ $0.id }), to: mainItem.id)
"Food" or "Beverages" items, all the items stored in their options property }
are shown on the screen. AppData.dataSource.apply(snapshot, to: .main, animatingDifferences: false)
}
For this feature to work, we must mirror this organization in a section func getItem(id: MainItems.ID) -> MainItems? {
snapshot. The section snapshot is created first and then the items are var item = AppData.items.first(where: { $0.id == id })
if item == nil {
added with the append([ItemIdentifierType], to: ItemIdentifierType?) method, where for main in AppData.items {
the to argument is the identifier of the parent to which the children belong if let found = main.options.first(where: { $0.id == id }) {
or nil if the item to be added is a parent item, as shown next. item = found
break
}
Listing 11-23: Creating a hierarchical list }
}
 return item
import UIKit }
}
class MyCollectionViewController: UICollectionViewController { 
override func viewDidLoad() {
super.viewDidLoad()
let config = UICollectionLayoutListConfiguration(appearance: .plain)
The prepareSnapshot() method in Listing 11-23 creates the
let layout = UICollectionViewCompositionalLayout.list(using: config) NSDiffableDataSourceSectionSnapshot structure and then adds all the items from
collectionView.collectionViewLayout = layout the items array. First, we add the parent and then the children stored in the
prepareDataSource() parent's options property. The rest of the code is similar to previous
prepareSnapshot() examples, but because we are working with nested items, we use a
}
func prepareDataSource() { method called getItem() to get the item from the item's identifier. First, the
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, method compares the identifier with the parent's identifier. If the item is
MainItems.ID> { cell, indexPath, itemID in
if let item = self.getItem(id: itemID) {
not found (it is not a parent item), we iterate through the items array to look
var config = cell.defaultContentConfiguration() for the item inside the options array. The item is returned, and the cell is
config.text = item.name configured as before.
cell.contentConfiguration = config
cell.accessories = item.options != nil ? [.outlineDisclosure()] : [] Notice that the cell includes an Outline Disclosure accessory. This is an
} accessory specifically designed to work with hierarchical lists (also known
}
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, MainItems.ID>
as expandable/collapsible outlines). The accessory shows an arrow that
(collectionView: collectionView) { (collection, indexPath, itemID) in points down when the parent item is expanded, as shown below.
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
itemID)
} Figure 11-20: Hierarchical list

CHAPTER 12 - SPLIT VIEW CONTROLLERS

Do It Yourself: Replace the model in your project with the one in


Listing 11-22. Update the MyCollectionViewController class with the code
in Listing 11-23. Run the application and tap on a parent item to
expand it.
12.1 Universal Container

The container view controllers studied so far are good for devices with
small screens, such as iPhones and iPods, but the space available in devices
like the iPads and large iPhones in landscape mode demand a more
elaborated design. UIKit includes a subclass of UIViewController called
UISplitViewController that can present up to three scenes at a time: the 
primary scene, the supplementary scene, and the secondary scene.
iPhones in portrait mode present a different configuration; the columns are
Figure 12-1: Split View Controller with three columns displayed as in a Navigation Controller. They replace one another and
buttons are included to navigate back.

Figure 12-3: Split View Controller in iPhones in portrait mode

The Primary and Supplementary columns are removable, and they are 
presented on the screen depending on the number of columns and the
space available. By default, on iPads in landscape mode, only one of the Split View Controllers can be configured with two or three columns
removable columns is shown. In a two-column design, the Split View (Primary and Secondary, or Primary, Supplementary, and Secondary) and
Controller displays the Primary column, while in a three-column design the the removable columns may be shown on the left of the Secondary column
Supplementary column is shown instead. But on iPads on portrait mode or on top of it. This style is defined by the controller's display mode. There
and large iPhones in landscape mode, only the Secondary column is are a total of six display modes available.
displayed, and a button is provided in the navigation bar to open the
removable columns. Figure 12-4: Display modes for Split View Controllers

Figure 12-2: Button on the navigation bar to open removable columns

Split View Controller


Split View Controllers are created from the UISplitViewController class. As with
any other view, they can be added to the interface from code or to the
Storyboard from the Library.

 UISplitViewController(style: Style)—This initializer creates a Split


View Controller of the type specified by the style argument. The
argument is an enumeration with the values unspecified (defines a
classic Split View Controller), doubleColumn (defines a controller with
two columns), and tripleColumn (defines a controller with three
columns).

Figure 12-5: Split View Controller option in the Library

The option in the Library adds to the Storyboard a two-column Split View
Controller, which includes the scenes for the Primary and Secondary
columns. The Primary scene is embedded in a visible Navigation Controller,
like those we have implemented before. On the other hand, the Secondary
scene is a single view embedded in a Navigation Controller that is
automatically assigned to the scene by the Split View Controller, as shown
below.

Figure 12-6: Split View Controller in the Storyboard


 The panel includes the Style option to configure the Split View Controller
with two or three columns (Figure 12-7, number 1), and the Display Mode
By default, this Split View Controller is configured with two columns and Behavior options to change the controller's display mode and
(Primary and Secondary), but we can modify this configuration and other behavior, respectively (Figure 12-7, number 2). The Display Mode
values from the Attributes Inspector panel. determines how the columns are shown on the screen, and the split
Behavior determines how the Secondary column is going to behave when
Figure 12-7: Split View Controller configuration the removable columns are visible. There are three values available: Tile,
Overlay, and Displace.

Figure 12-8: Split behavior

If we change the configuration of the Split View Controller to three


columns, an additional scene is added to the Storyboard, but we can
always remove or add these scenes manually. After adding the scenes, all

we need to do is to control-drag a line from the Split View Controller to the Split View Controller Configuration
scene and select the type of segue according to their role on the interface.

For this purpose, the Storyboard defines four special segues called
Relationship Segues, as shown next.
Because Split View Controllers automatically expand and collapse to adapt
to the space available, they are perfect to create universal applications that
Figure 12-9: Relationship Segues for Split View Controllers
work on iPads and iPhones, but they still require some configuration. In
addition to the options included in the Attributes Inspector panel, the
UISplitViewController class includes the following properties.

preferredDisplayMode—This property sets or returns the


preferred display mode. It is an enumeration called DisplayMode with
the values secondaryOnly, oneBesideSecondary, oneOverSecondary,
 twoBesideSecondary, twoOverSecondary, twoDisplaceSecondary, and automatic.

Another useful segue when working with Split View Controllers is the Show displayMode—This property returns the current display mode. It is
Detail segue. This segue is used to connect the Primary or Supplementary an enumeration called DisplayMode with the values secondaryOnly,
scenes to the Secondary scene (the segue is triggered from the Primary or oneBesideSecondary, oneOverSecondary, twoBesideSecondary, twoOverSecondary,

Supplementary scenes to open a scene in the Secondary column). twoDisplaceSecondary, and automatic.

preferredSplitBehavior—This property sets of returns the


Figure 12-10: Show Detail Segue preferred split behavior, which determines how the Secondary scene
should appear in relation to the removable columns. It is an
enumeration called SplitBehavior with the values tile, overlay, displace, and
automatic.

isCollapsed—This property returns a Boolean value that determines


the state of the interface. The value true means that the interface is
collapsed (the controller is presenting only one column and it cannot
be expanded), and the value false means the interface is expanded (the
 controller is presenting or can present two or more columns).
presentsWithGesture—This property sets or returns a Boolean
value that determines whether a hidden view can be shown with a
swipe gesture. Some configurations, like the iPad in portrait mode,
present only one view and allow the user to make the other visible by The UISplitViewController class also includes the following properties to
swiping the finger or pressing a button. This property activates or suggest a size for the Primary and Supplementary columns.
deactivates these features.
showsSecondaryOnlyButton—This property sets or returns a preferredPrimaryColumnWidthFraction—This property sets or
Boolean value that determines whether the navigation bar of the returns a CGFloat value that determines the width of the Primary
Secondary column includes a button to show or hide the removable column in relation to the Secondary column. The value must be
columns. specified with a number between 0.0 and 1.0. For example, a value of
0.5 gives the Primary column a size of 50% the width of the space
Besides the tools provided by the Split View Controller to show or hide the available.
removable views, the UISplitViewController class includes the following preferredPrimaryColumnWidth—This property sets or returns a
methods to control the scenes. CGFloat value with the preferred width of the Primary column.
primaryColumnWidth—This property returns a CGFloat value with
viewController(for: Column)—This method returns a reference to the width of the Primary column.
the view controller presented in the column specified by the for
minimumPrimaryColumnWidth—This property sets or returns a
argument. The argument is an enumeration with the values primary,
value that determines the minimum width of the Primary
CGFloat
supplementary, secondary, and compact.
column.
setViewController(UIViewController?, for: Column)—This
maximumPrimaryColumnWidth—This property sets or returns a
method assigns a new view controller to the column specified by the
value that determines the maximum width of the Primary
CGFloat
for argument. The first argument is a reference to the view controller
column.
(the scene) we want to show in the column, and the for argument is
an enumeration with the values primary, supplementary, secondary, and preferredSupplementaryColumnWidthFraction—This property
compact. sets or returns a CGFloat value that determines the width of the
Supplementary column in relation to the Secondary column. The
show(Column)—This method makes a column visible. The argument
value must be specified with a number between 0.0 and 1.0. For
is an enumeration with the values primary, supplementary, secondary, and
example, a value of 0.5 gives the Supplementary column a size of 50%
compact.
the width of the space available.
hide(Column)—This method hides a column. The argument is an
preferredSupplementaryColumnWidth—This property sets or
enumeration with the values primary, supplementary, secondary, and
returns a CGFloat value with the preferred width of the Supplementary
compact.
column.

supplementaryColumnWidth—This property returns a CGFloat Split View Controller Delegate


value with the width of the Supplementary column.

minimumSupplementaryColumnWidth—This property sets or
returns a CGFloat value that determines the minimum width of the Split View Controllers can also designate a delegate to report changes in
Supplementary column. the columns. The delegate must conform to the UISplitViewControllerDelegate
maximumSupplementaryColumnWidth—This property sets or protocol. The following are the methods defined in this protocol to manage
a two or three-columns Split View Controller.
returns a CGFloat value that determines the maximum width of the
Supplementary column.
splitViewController(UISplitViewController,
topColumnForCollapsingToProposedTopColumn: Column)—
IMPORTANT: The values set by these properties are suggestions to
the Split View Controller, but the controller decides what size, mode, This method is called by the Split View Controller on its delegate
and behavior to apply depending on the device and space available. before collapsing into one column. The method receives a Column
value representing the column the controller is proposing to show and
must return another Column value representing the column we really
want to show.
splitViewController(UISplitViewController, willHide: Column)
—This method is called by the Split View Controller on its delegate
when a column is going to be hidden. The method receives a Column
value representing the affected column.
splitViewController(UISplitViewController, willShow: Column)
—This method is called by the Split View Controller on its delegate
when a column is going to be shown. The method receives a Column
value representing the affected column.
splitViewControllerDidCollapse(UISplitViewController)—This
method is called by the Split View Controller on its delegate to report
that the interface has collapsed.
splitViewControllerDidExpand(UISplitViewController)—This
method is called by the Split View Controller on its delegate to report
that the interface has expanded.
splitViewController(UISplitViewController, 12.2 Implementing Split View Controllers
displayModeForExpandingToProposedDisplayMode: 
DisplayMode)—This method is called by the Split View Controller on
its delegate when it needs to know which display mode to use when An application based on a Split View Controller works like any other we
the interface is expanding. The method must return a DisplayMode have built so far. The elements of the interface are added to the scenes and
value. view controllers are assigned to the scenes to manage their content. For
example, based on the scenes provided by a two-column Split View
Controller, we can create an application to manage the same grocery list
used in previous chapters. When an item is selected in the Primary column,
the Secondary column shows the item's thumbnail, name, and calories.
Figure 12-11, below, shows the modifications we need to introduce to the
scenes for the Primary and Secondary columns to manage this information.

Figure 12-11: Split View controller with custom Primary and Secondary
scenes

Collection Views in Chapter 11), but it also must perform the Show Detail
In this example, we have replaced the Table View Controller included in the segue to open a new scene every time an item is selected.
standard Split View Controller with a Collection View Controller. The scene
for the Secondary column was embedded in a Navigation Controller, and Listing 12-1: Controlling the Primary scene
an Image View and two labels have been added to show the item's values 
on the screen. Notice that the Primary scene was connected to the import UIKit
Navigation Controller of the Secondary scene with a Show Detail segue, so
class PrimaryViewController: UICollectionViewController {
every time an item is selected in the Primary column, its values are shown var selected: ItemsData.ID!
in the Secondary column.
override func viewDidLoad() {
super.viewDidLoad()
Do It Yourself: Create a new project. Erase the initial scene and let config = UICollectionLayoutListConfiguration(appearance: .sidebar)
let layout = UICollectionViewCompositionalLayout.list(using: config)
add a Split View Controller from the Library (Figures 12-5 and 12-6). collectionView.collectionViewLayout = layout
Select the Split View Controller and activate the option Is Initial View
prepareDataSource()
Controller from the Attributes Inspector panel. Replace the Table prepareSnapshot()
View Controller with a Collection View Controller and connect it to }
func prepareDataSource() {
the Navigation Controller with a Root View Controller segue (Figure let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell,
12-11, number 1). Double click on the Navigation Bar to change its ItemsData.ID> { cell, indexPath, itemID in
if let item = AppData.items.first(where: { $0.id == itemID }) {
title to "Food". Embed the Secondary scene in a Navigation var config = cell.defaultContentConfiguration()
Controller and add an Image View and two labels to the scene config.text = item.name
cell.contentConfiguration = config
(Figure 12-11, number 2). Control-drag a line from the Collection }
}
View Controller to the Navigation Controller of the Secondary scene
AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
to create a Show Detail segue (Figure 12-11, number 3). Give the (collectionView: collectionView) { collection, indexPath, itemID in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
segue the identifier "showSecondary". itemID)
}
}
As always, the first step is to define the data model. For this example, we
func prepareSnapshot() {
are going to use the same model implemented for Table Views in Chapter var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
10 (see Listing 10-1). All we need to remember is to define the diffable data snapshot.appendSections([.main])
snapshot.appendItems(AppData.items.map({ $0.id }))
source to work with a Collection View (var dataSource: AppData.dataSource.apply(snapshot)
UICollectionViewDiffableDataSource<Sections, ItemsData.ID>!). }
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath:
The view controller for the Primary scene must define the diffable data IndexPath) {
source and the snapshot to provide the data to the Collection View (see if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) {
selected = itemID
performSegue(withIdentifier: "showSecondary", sender: nil) class SecondaryViewController: UIViewController {
} @IBOutlet weak var itemThumbnail: UIImageView!
} @IBOutlet weak var itemName: UILabel!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { @IBOutlet weak var itemCalories: UILabel!
if segue.identifier == "showSecondary" { var selected: ItemsData.ID!
if let navigator = segue.destination as? UINavigationController {
let controller = navigator.topViewController as! SecondaryViewController override func viewDidLoad() {
controller.selected = selected super.viewDidLoad()
} if selected != nil {
} if let item = AppData.items.first(where: { $0.id == selected! }) {
} itemThumbnail?.image = UIImage(named: item.image)
} itemName?.text = item.name
 itemCalories?.text = "Calories: \(item.calories)"
}
}
When possible, Apple recommends using a Collection View List with a side }
bar configuration to display the content of the Primary and Supplementary }

columns. In this example, we configure the layout with a sidebar appearance
and the cells to display the item's name, so we get a list of items on the
By default, iPads display the Primary and Secondary columns side by side in
Primary column. When an item is selected, the Collection View Controller
landscape, and only the Secondary column in portrait mode. In landscape
calls the collectionView(UICollectionView, didSelectItemAt:) method. In this method,
mode, the Split View Controller includes a button in the navigation bar to
we get the selected item with the itemIdentifier() method, store the identifier
hide or show the Primary column (Figure 12-12, number 1), but in portrait
in a property, and perform the Show Detail segue (identified with the name
mode the Primary column can be opened with a swipe gesture from the
"showSecondary").
left or by pressing the back button included in the navigation bar (Figure
In the prepare() method, the value of the selected property is sent to the view
12-12, number 2).
controller of the Secondary scene to show the values on the screen. Notice
that the Show Detail segue is connected to the Navigation Controller, so we
Figure 12-12: Split View Controller in landscape and portrait modes
first get a reference to the Navigation Controller of the Secondary scene,
then read the topViewController property of the Navigation Controller to get a
reference to the view controller of the Secondary scene, and finally pass
the value of the selected property to this view controller.
All the view controller of the Secondary scene needs to do is to get the
item from the identifier and show its values on the screen. 

Listing 12-2: Displaying the item in the Secondary scene


Do It Yourself: Create a Swift file called ApplicationData.swift with

the model in Listing 10-1 (Chapter 10). Update the definition of the
import UIKit
dataSource property to work with Collection Views (var dataSource:

UICollectionViewDiffableDataSource<Sections, ItemsData.ID>!).
Create a file Swipe Gesture
with a subclass of UICollectionViewController called PrimaryViewController

and assign it to the Primary scene. Update the class with the code in
Listing 12-1. Create a file with a subclass of UIViewController called As we already mentioned, Split View Controllers include a gesture and a
SecondaryViewController and assign it to the Secondary scene. Update button that allow the user to change the display mode (show or hide the
the class with the code in Listing 12-2. Connect the elements in the Primary column), but if we want to provide our own tools to control the
scene to their respective Outlets. Download the food thumbnails columns, we can remove these features by assigning the value false to the
from our website and add them to the Assets Catalog. Run the presentsWithGesture property.

application in the iPad and iPhone simulators. Rotate the simulator The property can be modified from any view controller, but a good practice
to see how the Split View Controller responds in every device. is to create a subclass of the UISplitViewController class to take care of the
controller's configuration.

Listing 12-3: Configuring the Split View Controller



import UIKit

class MySplitViewController: UISplitViewController {


override func viewDidLoad() {
super.viewDidLoad()
presentsWithGesture = false
}
}

The UISplitViewController subclass in Listing 12-3 assigns the value true to the
presentsWithGesture property as soon as the Split View Controller is loaded.
Now the Split View Controller doesn't offer the built-in features to allow
the user to manipulate the interface.

Do It Yourself: Create a subclass of the UISplitViewController class


called MySplitViewController and assign it to the Split View Controller in
the Storyboard. Complete the class with the code in Listing 12-3. Run
the application. You shouldn't see the button to remove the Primary
column anymore and shouldn't be able to open the column with a if splitViewController?.displayMode == .secondaryOnly {
splitViewController?.show(.primary)
swipe gesture either. } else {
splitViewController?.hide(.primary)
}
Of course, if we still want the user to be able to show or hide the }
removable columns, we must provide our own tools. For this purpose, the }
}
UIViewController class defines the splitViewController property to access the Split

View Controller, and the UISplitViewController class includes the show() and
hide() methods to show or hide a column. For instance, we can add a button In this example, we create a UIBarButtonItem object and add it to the bar as
to the Navigation Bar of the Secondary scene and then call the show() or soon as the Secondary scene is loaded. When pressed, the button calls the
hide() methods depending on what we need to do. showColumn() method where we check the current state of the interface and
show or hide the scenes accordingly. If the interface is collapsed, it means
Listing 12-4: Managing the columns from code that we are in an iPhone in portrait mode, so we hide the Secondary scene
 to force the system to transition back to the Primary scene. If not, we show
import UIKit or hide the Primary scene depending on the display mode.
class SecondaryViewController: UIViewController {
@IBOutlet weak var itemThumbnail: UIImageView! Figure 12-13: Custom button for the Split View Controller
@IBOutlet weak var itemName: UILabel!
@IBOutlet weak var itemCalories: UILabel!
var selected: ItemsData.ID!

override func viewDidLoad() {


super.viewDidLoad() 
let buttonMode = UIBarButtonItem(title: "Menu", image: nil, primaryAction:
UIAction(handler: { [unowned self] action in
self.showColumn() Do It Yourself: Update the SecondaryViewController class with the code
}), menu: nil) in Listing 12-4 and run the application. You should see the Menu
navigationItem.leftBarButtonItem = buttonMode
button in the Secondary scene. Press the button to hide or show the
if selected != nil {
Primary column.
if let item = AppData.items.first(where: { $0.id == selected! }) {
itemThumbnail?.image = UIImage(named: item.image)
itemName?.text = item.name
itemCalories?.text = "Calories: \(item.calories)"
}
}
}
func showColumn() {
if splitViewController?.isCollapsed == true {
splitViewController?.hide(.secondary)
} else {

Display Mode Do It Yourself: Update the MySplitViewController class with the code
in Listing 12-5 (if you haven't created this subclass yet, you can do it

as explained for the example in Listing 12-3). Run the application.
Due to the reduced size of the screens in iPads in portrait mode and large The Primary column should always be visible, regardless of the
iPhones in landscape mode, the Split View Controller presents the columns orientation, as illustrated in Figure 12-14.
with a display mode of type secondaryOnly, which means that only the
secondary column is visible. But we can recommend an alternative mode
and split behavior by assigning a different value to the preferredDisplayMode
and the preferredSplitBehavior properties.

Listing 12-5: Changing the Split View Controller display mode



import UIKit

class MySplitViewController: UISplitViewController {


override func viewDidLoad() {
super.viewDidLoad()
presentsWithGesture = false
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .tile
}
}

The code in Listing 12-5 recommends the Split View Controller to use the
mode oneBesideSecondary and the split behavior tile (the columns share the
space available). In consequence, the Split View Controller displays the
Primary column beside the Secondary column when possible.

Figure 12-14: Custom display mode and split behavior in iPads


Default Item
Do It Yourself: Update the viewDidLoad() method in your

class with the code in Listing 12-6 and run the
SecondaryViewController

When our application is launched, no item has been selected by the user application. You should see the first item in the model on the screen.
yet, so the Secondary scene has nothing to show. Depending on the
characteristics of our application, we may provide a placeholder or just
select an item at random. For instance, we can modify our
SecondaryViewController class to get the first item in the model when no item
was selected.

Listing 12-6: Selecting the item by default



override func viewDidLoad() {
super.viewDidLoad()
let buttonMode = UIBarButtonItem(title: "Menu", image: nil, primaryAction: UIAction(handler: {
[unowned self] action in
self.showColumn()
}), menu: nil)
navigationItem.leftBarButtonItem = buttonMode

if selected == nil {
if let item = AppData.items.first {
selected = item.id
}
}
if let item = AppData.items.first(where: { $0.id == selected! }) {
itemThumbnail?.image = UIImage(named: item.image)
itemName?.text = item.name
itemCalories?.text = "Calories: \(item.calories)"
}
}

Listing 12-6 are the changes we need to introduce to the viewDidLoad()


method of the SecondaryViewController class to show an item by default. If the
value of the selected property is nil, which means no item was selected by
the user, we get the first item in the items array and assign it to the selected
property, so that item is shown on the screen.

Default Column For this example, we have decided to designate our UISplitViewController
subclass as the Split View Controller delegate, but any other view

controller would work as well. In this case, we implement the protocol
method and return the value primary, so the Split View Controller shows the
When the app is launched, the Split View Controller decides how to display
Primary column first when the app is launched in a collapsed interface.
the columns. If there is only space available for one column, by default it
will show the Secondary. This means that, for example, when we launch
Do It Yourself: Update the MySplitViewController class with the code
the app in an iPhone in portrait mode, we will always see the Secondary
in Listing 12-7. Run the application on the iPhone simulator in
column until an action is performed. The UISplitViewControllerDelegate protocol
defines the splitViewController(UISplitViewController, portrait mode. You should see the Primary scene on the screen.
topColumnForCollapsingToProposedTopColumn:) method to change this predefined
behavior. The method is called by the Split View Controller to know what
column we want to show when the interface is collapsed. For instance, the
view controller in the following example conforms to the
UISplitViewControllerDelegate protocol and implements this method to ask the
Split View Controller to show the Primary column first.

Listing 12-7: Declaring the initial column for a collapsed interface



import UIKit

class MySplitViewController: UISplitViewController, UISplitViewControllerDelegate {


override func viewDidLoad() {
super.viewDidLoad()
presentsWithGesture = false
preferredDisplayMode = .oneBesideSecondary
preferredSplitBehavior = .tile
delegate = self
}
func splitViewController(_ svc: UISplitViewController,
topColumnForCollapsingToProposedTopColumn proposedTopColumn:
UISplitViewController.Column) -> UISplitViewController.Column {
return .primary
}
}

Compact Scene
Figure 12-16: Compact scene in a collapsed interface

With the delegate method implemented in the previous section we can


select the column we want to show first from those available, but Split
View Controllers can also include an additional scene that is only shown in
a collapse interface. The scene is added to the Split View Controller with a
Compact View Controller segue or introduced to the Storyboard by
selecting the option Use Separate View Controller from the Attributes
Inspector panel (see Figure 12-7). Once we select this option, the Compact
scene is added to the Storyboard (Figure 12-15, number 1).

Figure 12-15: Compact scene

Do It Yourself: Select the Split View Controller in the Storyboard


and activate the Use Separate View Controller option in the
Attributes Inspector panel. You should see a new scene in the
Storyboard connected to the Split View Controller with a Compact
View Controller segue. Remove the delegate method added to the
UISplitViewController subclass in Listing 12-7 to let the Split View
Controller select the initial scene. Add a label to identify the scene
 and run the application on the iPhone simulator in portrait mode.
You should see the interface of Figure 12-16.
The Compact scene is used when the interface for a Compact Size Class is
radically different from the rest. Once this scene is added to the Split View
Controller, it is displayed every time the interface collapses, unless we
specify otherwise from the delegate method.

Three-Column Design Do It Yourself: Remove the Compact scene added in the previous
example and add a Collection View Controller. Connect this scene to

the Split View Controller with a Supplementary View Controller
Split View Controllers can be configured with two or three columns. So far, segue (Figure 12-17, number 1). Remove all the configuration
we have been using a two-column design, but we can easily add an changes added to the UISplitViewController subclass before to allow the
additional column by changing the Style option in the Attributes Inspector Split View Controller to provide the standard buttons to manage the
panel to Triple Column (see Figure 12-7, number 1). Once we select this columns. Remove the Show Detail segue and add a new one from
option, a Supplementary scene is added to the Storyboard and connected the Supplementary scene to the Navigation Controller of the
to the Split View Controller with a Supplementary View Controller segue. Secondary scene (Figure 12-17, number 2). The rest of the interface
Xcode adds a single view to the Storyboard, but we can replace it with any is the same as before, but because we are going to use the Primary
type of scene we want. For our example, we are going to use the Primary scene to show the list of categories, you should change its title to
scene to show a list of categories, and the Supplementary scene to show Categories, as we did in the interface in Figure 12-17.
the items belonging to each category, so we need a Collection View
Controller. Figure 12-17, below, shows what the interface looks like after
For this example, we need to organize the items in categories. The
we replace the scene with a Collection View Controller (number 1).
following model defines a class to store the categories called Categories,
which includes an array of ItemsData objects to store the items for each
Figure 12-17: Scenes for a three-column Split View Controller
category.

Listing 12-8: Defining a model to work with a three-column Split View


Controller

import UIKit

enum Sections {
case main
}
class Categories: Identifiable {
var id: UUID = UUID()
var name: String
var items: [ItemsData]

init(_ name: String, _ items: [ItemsData]) {


self.name = name
 self.items = items
}
}
class ItemsData: Identifiable { categories in the Primary scene and the items in the Supplementary scene.
var id: UUID = UUID()
var name: String The following is the code for the Primary view controller.
var image: String
var calories: Int
var selected: Bool
Listing 12-9: Showing the categories

init(_ name: String, _ image: String, _ calories: Int, _ selected: Bool) { import UIKit
self.name = name
self.image = image class PrimaryViewController: UICollectionViewController {
self.calories = calories override func viewDidLoad() {
self.selected = selected super.viewDidLoad()
} let config = UICollectionLayoutListConfiguration(appearance: .sidebar)
} let layout = UICollectionViewCompositionalLayout.list(using: config)
struct ApplicationData { collectionView.collectionViewLayout = layout
var dataSourceCategories: UICollectionViewDiffableDataSource<Sections, Categories.ID>!
var dataSource: UICollectionViewDiffableDataSource<Sections, ItemsData.ID>! prepareDataSource()
var categories: [Categories] = [] prepareSnapshot()
var selectedCategory: Categories! }
func prepareDataSource() {
init() { let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell,
categories.append(Categories("Breads", [ ItemsData("Bagels", "bagels", 250, false), Categories.ID> { cell, indexPath, itemID in
ItemsData("Brownies", "brownies", 466, false), ItemsData("Cookies", "cookies", 502, false), if let item = AppData.categories.first(where: { $0.id == itemID }) {
ItemsData("Donuts", "donuts", 452, false) ])) var config = cell.defaultContentConfiguration()
categories.append(Categories("Cereals", [ ItemsData("Granola", "granola", 471, false), config.text = item.name
ItemsData("Oatmeal", "oatmeal", 68, false) ])) cell.contentConfiguration = config
categories.append(Categories("Vegetables", [ ItemsData("Lettuce", "lettuce", 15, false), }
ItemsData("Potatoes", "potato", 77, false), ItemsData("Tomatoes", "tomato", 18, false) ])) }
categories.append(Categories("Dairy", [ ItemsData("Butter", "butter", 717, false), AppData.dataSourceCategories = UICollectionViewDiffableDataSource<Sections,
ItemsData("Cheese", "cheese", 402, false), ItemsData("Milk", "milk", 42, false), ItemsData("Yogurt", Categories.ID>(collectionView: collectionView) { collection, indexPath, itemID in
"yogurt", 59, false) ])) return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
categories.append(Categories("Beverages", [ ItemsData("Coffee", "coffee", 0, false), itemID)
ItemsData("Juice", "juice", 23, false), ItemsData("Lemonade", "lemonade", 40, false) ])) }
}
selectedCategory = categories.first func prepareSnapshot() {
} var snapshot = NSDiffableDataSourceSnapshot<Sections, Categories.ID>()
} snapshot.appendSections([.main])
var AppData = ApplicationData() snapshot.appendItems(AppData.categories.map({ $0.id }))
 AppData.dataSourceCategories.apply(snapshot)
}
The model includes properties to store two diffable data sources, one for override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath:
IndexPath) {
the categories (dataSourceCategories) and another for the items (dataSource). if let categoryID = AppData.dataSourceCategories.itemIdentifier(for: indexPath), let category =
Using these data sources, we can define the view controllers to list the AppData.categories.first(where: { $0.id == categoryID }) {
if let controller = splitViewController?.viewController(for: .supplementary) as?
SupplementaryViewController {

AppData.selectedCategory = category
controller.prepareSnapshot() override func viewDidLoad() {
super.viewDidLoad()
if splitViewController?.isCollapsed == true { let config = UICollectionLayoutListConfiguration(appearance: .sidebarPlain)
splitViewController?.show(.supplementary) let layout = UICollectionViewCompositionalLayout.list(using: config)
} collectionView.collectionViewLayout = layout
}
} navigationItem.title = "Food"
}
} prepareDataSource()
 prepareSnapshot()
}
func prepareDataSource() {
The view controller in Listing 12-9 shows the list of categories, so the let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell,
diffable data source and the snapshot are defined with the Categories.ID type ItemsData.ID> { cell, indexPath, itemID in
if let item = AppData.selectedCategory?.items.first(where: { $0.id == itemID }) {
and the categories array. Again, we implement the collectionView(UICollectionView, var config = cell.defaultContentConfiguration()
didSelectItemAt:) method to respond to the selection of a cell. In this method, config.text = item.name
we get the item's identifier and send it to the Supplementary scene. cell.contentConfiguration = config
}
The Supplementary scene displays the items that belong to the category }
selected by the user, so we get the Categories object that represents the AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
(collectionView: collectionView) { collection, indexPath, itemID in
selected category from the categories array and store it in a property in the return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
model (We store it in a property in the model to make it available for the itemID)
}
Supplementary and the Secondary scenes). After the selected category is
}
assigned to the selectedCategory property, we call the prepareSnapshot() method func prepareSnapshot() {
in the Supplementary scene to update the Collection View, and then var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
arrange the columns on the screen. By default, the Split View Controller let items = AppData.selectedCategory?.items.map({ $0.id })
can manage the columns in expanded interfaces without any help, but we snapshot.appendItems(items ?? [])
AppData.dataSource.apply(snapshot, animatingDifferences: false)
still have to call the show() method to show the Supplementary scene in a }
collapsed interface. override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath:
When opened, the Supplementary scene reads the selected category from IndexPath) {
if let itemID = AppData.dataSource.itemIdentifier(for: indexPath) {
the model and shows the items on the screen. selected = itemID
performSegue(withIdentifier: "showSecondary", sender: nil)
}
Listing 12-10: Showing the items in a category }
 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSecondary" {
import UIKit
if let navigator = segue.destination as? UINavigationController {
let controller = navigator.topViewController as! SecondaryViewController
class SupplementaryViewController: UICollectionViewController {
controller.selected = selected
var selected: ItemsData.ID!
}
} }
} }
} 

In iPads in landscape mode, the columns are shown by default, but in iPads
This view controller performs the same tasks as the view controller for the in portrait mode and large iPhones in landscape mode, the removable
Primary scene, but now the snapshot is created from the items property of columns are hidden and only the Secondary column is visible, and in
the selected category, so only the items that belong to that category are iPhones in portrait mode (collapsed interfaces) the columns are shown one
shown on the screen. at a time, but buttons are always provided to access the rest of the
When an item is selected in the Supplementary scene, it must be sent to columns.
the Secondary scene to show the values on the screen. This is the same
process used before for a two-column Split View Controller; we assign the Figure 12-18: Three-column Split View Controller on an iPhone
item to the selected property and perform the Show Detail segue.
No changes are required for the view controller that controls the
Secondary scene, except that this time we get the item by default from the
selected category (this is why we assigned the selected category to a
property in the model).

Listing 12-11: Showing the item's values
 Do It Yourself: Update the ApplicationData.swift file in your project
import UIKit with the model in Listing 12-8. Update the PrimaryViewController class
class SecondaryViewController: UIViewController { and the SecondaryViewController class with the codes in Listings 12-9
@IBOutlet weak var itemThumbnail: UIImageView!
and 12-11. Create a subclass of UICollectionViewController called
@IBOutlet weak var itemName: UILabel!
@IBOutlet weak var itemCalories: UILabel! SupplementaryViewController and assign it to the Supplementary scene.
var selected: ItemsData.ID!
Complete this class with the code in Listing 12-10. Run the
override func viewDidLoad() { application and test it in different devices and orientations to see
super.viewDidLoad()
if selected == nil {
how the columns work.
if let item = AppData.selectedCategory.items.first {
selected = item.id
}
}
if let item = AppData.selectedCategory?.items.first(where: { $0.id == selected! }) {
itemThumbnail?.image = UIImage(named: item.image)
itemName?.text = item.name
itemCalories?.text = "Calories: \(item.calories)"
}

Secondary Only Button Do It Yourself: Create or update the UISplitViewController subclass


with the code in Listing 12-12. Run the application in the iPad

simulator in landscape mode. You should see the Supplementary and
Split View Controllers include buttons to open the removable columns and Secondary columns on the screen. Press the Secondary Only button
offer the possibility to tap anywhere on the Secondary column to hide to hide or show the Supplementary column.
them. But in a three-column design, we can also add a standard button for
this purpose. All we need to do to activate this feature is to assign the
value true to the showsSecondaryOnlyButton property, as in the following
example.

Listing 12-12: Adding the Secondary Only button



import UIKit

class MySplitViewController: UISplitViewController {


override func viewDidLoad() {
super.viewDidLoad()
showsSecondaryOnlyButton = true
}
}

The button is automatically added to the navigation bar of the Secondary


scene when a removable column is displayed on the side. The user can tap
this button to show or hide the removable column.

Figure 12-19: Secondary Only button


12.3 Modal Scenes
modalPresentationStyle—This property sets or returns the scene's

presentation style. It is an enumeration of type UIModalPresentationStyle
Besides showing and hiding columns, we can expand the interface with with the values automatic, fullScreen, pageSheet, formSheet, currentContext,
modal scenes and popovers. We have already introduced modal scenes in custom, overFullScreen, overCurrentContext, popover, blurOverFullScreen, and

Chapter 9. They are normal scenes but connected with a Present Modally none.
segue. In iPhones, these scenes open as a sheet on top of the current modalTransitionStyle—This property sets or returns the transition
scene, but in iPads and large iPhones in landscape mode they are style. It is an enumeration of type UIModalTransitionStyle with the values
presented with designs that take advantage of the larger screens. These coverVertical, flipHorizontal, crossDissolve, and partialCurl. Notice that some of
presentation styles are set by the segue. The options are available from the these transitions are only available for the Full Screen presentation
Attributes Inspector panel when the segue is selected.
style.

Full Screen presents a scene of the size of the screen over the present(UIViewController, animated: Bool, completion: Block)
current interface. —This method presents a modal scene. The first argument is a
Current Context presents a scene in the place and of the size of reference to the view controller of the scene we want to present, the
the designated area. animated argument determines whether the process will be animated,
Page Sheet presents a scene as a sheet that emerges from the and the completion argument is an optional closure that is executed
bottom of the screen. after the scene is presented on the screen.
Form Sheet presents a scene as a sheet over the current dismiss(animated: Bool, completion: Block?)—This method
interface and in the center of the screen. dismisses the scene. The animated argument is a Boolean value that
Over Full Screen presents a scene over the interface and with determines whether the process will be animated, and the
the size of the screen (but without hiding the current scenes).
completion argument is a closure that is executed after the scene is
Over Current Context presents a scene in the place and with
closed.
the size of the designated area (but without hiding the current
scene).
Modal scenes are used to present additional information. For instance, we
can connect a scene to the Secondary scene with an Image View to let the
Because modal scenes are not part of the main interface and are not
user see a larger image of the selected item.
added to the navigation stack of a Navigation Controller, we must provide a
way for the user to control them. In Chapter 9, we studied how to do this
Figure 12-20: Interface with a Modal Segue
with an Unwind Segue, but the UIViewController class also offers some
properties and methods to present and dismiss these scenes
programmatically.

The view controller for the new scene must include a property called
selected to receive the identifier of the selected item and then update the
Image View using the item's values. We called this view controller
PictureViewController.

Listing 12-14: Showing a large version of the item’s thumbnail



import UIKit

class PictureViewController: UIViewController {


@IBOutlet weak var bigThumbnail: UIImageView!
var selected: ItemsData.ID!

override func viewDidLoad() {


super.viewDidLoad()

if selected != nil {

if let item = AppData.selectedCategory?.items.first(where: { $0.id == selected! }) {
bigThumbnail.image = UIImage(named: item.image)
In Figure 12-20, we have modified the Secondary scene of our example to }
}
incorporate a button below the item’s thumbnail with the title "Expand” }
(number 1) and have connected this button with a Present Modally segue @IBAction func closeScene(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
to a scene that includes an Image View. Now we can pass the selected item }
from the Secondary scene to the new scene to show a larger picture on the }
screen. For this purpose, the SecondaryViewController class has to implement 

the prepare() method.


This view controller is very similar to the view controller used for the
Secondary scene. It defines the selected property to receive the selected
Listing 12-13: Sending the selected item to the new scene
item and shows the values on the screen (only the image in this case), but

it also includes an Action for the Close button to call the dismiss() method
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPicture" { and close the scene.
let controller = segue.destination as! PictureViewController Depending on the presentation style selected and the device running the
controller.selected = selected
} application, the modal scene will adopt different designs. For example, on
} an iPhone in portrait mode, the scene is always shown as a sheet with a

transition determined by the Transition option, but on an iPad the scene is
shown with the selected presentation style (in large iPhones in landscape
mode, a presentation other than Full Screen always defaults to Page View and the button with an Outlet called bigThumbnail and an Action
Sheet). Figure 12-21, below, is what we see when we present the scene called closeScene(). Complete the PictureViewController class with the code
with the Page Sheet mode on an iPad. in Listing 12-14 and add the method in Listing 12-13 to the
SecondaryViewController class. Run the application on the iPad simulator,
Figure 12-21: Page Sheet presentation style
select an item, and press the Expand button. You should see
something like Figure 12-21.

The modal scene in this example was presented as a Page Sheet. There are
two modes available for Page Sheets: large and medium. A large Page
Sheet is displayed at full height, and a medium Page Sheet is shown at
approximately half the height of the screen. By default, the presentation is
large, but we can set a different mode by modifying the controller before
the scene is shown on the screen.
UIKit defines a class called UIPresentationController to manage the
presentation of modal scenes. When a modal scene is created and
presented, an object of the UIPresentationController class is automatically
created to manage the presentation. Page Sheets are created from a
subclass of the UIPresentationController class called UISheetPresentationController.
This class includes a property called detents that we can use to define the
presentation mode. The property takes an array of objects of a class called
Detent, which includes the following type methods.

medium()—This type method returns a Detent object to create a
Do It Yourself: Add a button below the thumbnail in the Secondary medium Page Sheet.
scene with the title "Expand" (Figure 12-20, number 1). Add a scene large()—This type method returns a Detent object to create a large
to the Storyboard and connect the button to this scene with a Page Sheet.
Present Modally segue. Assign the segue the identifier
"showPicture" and the Presentation style Page Sheet. Add a button To provide access to the UISheetPresentationController object created for our
with the title "Close" and an Image View to the new scene modal scene, the UIViewController class includes the sheetPresentationController
(Remember to assign a higher Vertical Content Hugging Priority to property. From this property, we can modify the detents property to set the
the button if necessary). Create a subclass of UIViewController called Page Sheet's mode. These changes can be performed in the prepare()
PictureViewController and assign it to the new scene. Connect the Image method, before the segue is triggered, as shown next.

Listing 12-15: Defining a medium Page Sheet



override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPicture" {
let controller = segue.destination as! PictureViewController
controller.selected = selected

if let sheet = controller.sheetPresentationController {


sheet.detents = [.medium()]
}
}
}

The sheetPresentationController property only returns a value if the presentation


style is Page Sheet, so we check whether there is a value in the property
and then modify the object's detents property. This property takes an array
of Detent objects. By assigning an array with the value returned by the
medium() method, the Page Sheet is displayed at half the height of the

screen.
When the scene is presented as a Form Sheet or a Popover, we can specify
Figure 12-22: Medium Page Sheet
its size from the Attributes Inspector panel. The option is called Use
Preferred Explicit Size, and it appears at the bottom of the panel when the
scene is selected (Figure 12-22, number 1).

Figure 12-23: Use Preferred Explicit Size option


This is not the same option as the Simulated Size offered by the Size Preferred Explicit Size, and assign a size of 300 x 400 points (Figure
Inspector panel (Figure 7-14). The Simulated Size option determines the 12-23). Run the application, select an item, and press the Expand
size of the scene in the Storyboard, while the Use Preferred Explicit Size button. You should see something like Figure 12-24.
option sets the size the scene is going to have when the application is
running. We can set both options to the same values if we want to work on Adding scenes to the interface with Modal segues is easy, but there are
a scene that matches what the user is going to see on the screen. Figure times when our application requires the addition of the scenes dynamically
12-24, below, shows the modal scene from the previous example from code. There are several alternatives, but the easiest way to do it is to
presented on an iPad as a Form Sheet and with an explicit size of 300 x add the scene to the Storyboard, as always, but disconnected from the rest
400. of the interface. These scenes are not connected to other scenes with
segues, but they can be managed from the object created to represent the
Figure 12-24: Form Sheet presentation with an explicit size Storyboard.
When the app is executed, the Storyboard is loaded and processed. The
objects representing the scenes and their content are created and
connected with each other, but the system also creates an object that
represents the Storyboard itself. This object is instantiated from the
UIStoryboard class and offers the following methods to access its content.

instantiateInitialViewController()—This method instantiates and


returns the view controller of the initial scene in the Storyboard.
instantiateViewController(withIdentifier: String)—This method
instantiates and returns the view controller of the scene in the
Storyboard that was identified with the string specified by the
withIdentifier argument.

Independent scenes are added to the Storyboard the same way we do with
other scenes, the only difference is that they are not connected to the rest
 of the scenes. Once the scene is ready, the process to generate its content
and view controller are the same. For our example, we will add to our
Do It Yourself: Select the Modal segue and change the project the scene down below with a view controller called
SingleViewController.
Presentation style to Form Sheet. Select the scene, open the
Attributes Inspector panel, go to the bottom, activate the option Use
Figure 12-25: Single scene in the Storyboard

To be able to present this scene from code, we must assign it an identifier


from the Identity Inspector panel. The option is called Storyboard ID. For
our example, we identified the scene with the string "helpView".

Figure 12-26: Option to assign an identifier to a scene

To demonstrate how to open these scenes, we have incorporated a Bar


Button Item called "Help" to the Navigation Bar of the Primary scene.
When the button is pressed, the PrimaryViewController class has to instantiate
the view controller for this scene with the instantiateViewController() method
and present it with the present() method.

 Listing 12-17: Instantiating the scene from code



The view controller for this scene only needs an Action for the Close @IBAction func showHelp(_ sender: UIBarButtonItem) {
button. if let story = storyboard {
let controller = story.instantiateViewController(withIdentifier: "helpView") as!
SingleViewController
Listing 12-16: Dismissing a single scene controller.modalPresentationStyle = .pageSheet
present(controller, animated: true, completion: nil)

}
import UIKit }

class SingleViewController: UIViewController {
@IBAction func closeHelp(_ sender: UIButton) {
dismiss(animated: true, completion: nil) To provide access to the UIStoryboard object, the UIViewController class offers
} the storyboard property. In Listing 12-17, we read this property and execute
}
the instantiateViewController() method to create an instance of the

SingleViewController class. The instance is stored in the controller constant and
its scene is presented on the screen with the present() method and the Presentation Controller
Presentation style pageSheet.

Do It Yourself: Add a new scene to the Storyboard with a label and
As we already mentioned, when a modal scene is presented, an object of
a button (Figure 12-25). Create a UIViewController subclass called
the UIPresentationController class is automatically created to manage the
SingleViewController. Select the scene, open the Identity Inspector
presentation. This object is assigned to a property of the view controller
panel, and assign the subclass to the scene and the string that is being presented called presentationController. From this property, we
"helpView" to the Storyboard ID option (Figure 12-26). Connect the can access the object and read its current configuration. The following are
Close button to the SingleViewController class with an Action called its most frequently used properties.
closeHelp() and complete the class with the code in Listing 12-16. Add
a Bar Button Item called Help to the navigation bar of the Primary presentingViewController—This property returns a reference to
scene and connect it to the PrimaryViewController class with an Action the view controller that initiated the presentation.
called showHelp(). Complete the method with the code in Listing 12- presentedViewController—This property returns a reference to
17. Run the application, open the Primary column, and press the the view controller that is being presented.
Help button to open the scene.
containerView—This property returns a reference to the scene in
which the presentation occurs.
presentationStyle—This property returns the presentation style
used to present the scene. It is an enumeration of type
UIModalPresentationStyle with the values automatic, fullScreen, pageSheet,
formSheet, currentContext, custom, overFullScreen, overCurrentContext, popover,
blurOverFullScreen, and none.

adaptivePresentationStyle—This property returns the


presentation style used when the horizontal Size Class becomes
compact.

We can also designate a delegate for the UIPresentationController object to


change its configuration according to the current state of the interface. The
delegate must conform to the UIAdaptivePresentationControllerDelegate protocol
and implement its methods.

adaptivePresentationStyle(for: UIPresentationController)—
This method is called on the delegate to know the presentation style
to use when the horizontal Size Class becomes compact.
presentationController(UIPresentationController,
viewControllerForAdaptivePresentationStyle:
UIModalPresentationStyle)—This method is called on the delegate
to get the view controller to present for the style determined by the
second argument.

The UIPresentationController object has two presentation styles, one for the
normal state and another for the adaptive state. The adaptive state is the
state in which the horizontal Size Class is compact. This is the state we will
find in small iPhones or when the app is opened in an iPad in multitasking
mode. When a modal scene is presented in these conditions, the 
presentation style is automatically changed to pageSheet, but we can specify
the style we want implementing the protocol methods. The advantage of The scene on the left is the same introduced in Figure 12-25, and the one
these methods is that they are called not only to know the presentation on the right is the new scene we will open in the adaptive state (when the
style to use in the adaptive state but also to get the view controller we horizontal Size Class is compact). In this example, we just change the
want to present, and therefore we can designate a completely different position of the Close button, so on iPads the button will be shown at the
view controller for each state. The following example adds another scene bottom and on iPhones at the top, but other changes and elements may be
to our Storyboard to present when the interface is in an adaptive state. introduced to adapt the scene to each device and situation. To control this
new scene, we have created a class called iPhoneViewController with the
Figure 12-27: Two interfaces for the same modal scene Action for the button.

Listing 12-18: Dismissing the adaptive scene



import UIKit

class iPhoneViewController: UIViewController {


@IBAction func closeHelp(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}

override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath:
IndexPath) {
We now have the two scenes to present when the Help button is pressed, if let categoryID = AppData.dataSourceCategories.itemIdentifier(for: indexPath), let category =
so all that is left to do is to implement the protocol methods in the AppData.categories.first(where: { $0.id == categoryID }) {
if let controller = splitViewController?.viewController(for: .supplementary) as?
PrimaryViewController class to load the scene on the right every time the
SupplementaryViewController {
horizontal Size Class is Compact. AppData.selectedCategory = category
controller.prepareSnapshot()

Listing 12-19: Showing an alternative modal scene if splitViewController?.isCollapsed == true {


 splitViewController?.show(.supplementary)
}
import UIKit }
}
class PrimaryViewController: UICollectionViewController,
}
UIAdaptivePresentationControllerDelegate { @IBAction func showHelp(_ sender: UIBarButtonItem) {
override func viewDidLoad() { if let story = storyboard {
super.viewDidLoad()
let controller = story.instantiateViewController(withIdentifier: "helpView") as!
let config = UICollectionLayoutListConfiguration(appearance: .sidebar) SingleViewController
let layout = UICollectionViewCompositionalLayout.list(using: config) controller.modalPresentationStyle = .formSheet
collectionView.collectionViewLayout = layout
let presentation = controller.presentationController
prepareDataSource() presentation?.delegate = self
prepareSnapshot()
} present(controller, animated: true, completion: nil)
func prepareDataSource() { }
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell,
}
Categories.ID> { cell, indexPath, itemID in func adaptivePresentationStyle(for controller: UIPresentationController) ->
if let item = AppData.categories.first(where: { $0.id == itemID }) { UIModalPresentationStyle {
var config = cell.defaultContentConfiguration()
return .fullScreen
config.text = item.name }
cell.contentConfiguration = config func presentationController(_ controller: UIPresentationController,
}
viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) ->
} UIViewController? {
AppData.dataSourceCategories = UICollectionViewDiffableDataSource<Sections, var controller: iPhoneViewController!
Categories.ID>(collectionView: collectionView) { collection, indexPath, itemID in if style == .fullScreen {
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: if let story = storyboard {
itemID) controller = story.instantiateViewController(withIdentifier: "iPhoneView") as?
} iPhoneViewController
} }
func prepareSnapshot() { }
var snapshot = NSDiffableDataSourceSnapshot<Sections, Categories.ID>() return controller
snapshot.appendSections([.main]) }
snapshot.appendItems(AppData.categories.map({ $0.id })) }
AppData.dataSourceCategories.apply(snapshot) 
}

As always, the first thing we need to do is to conform to the protocol and


declare the object as the delegate. Although we have done this dozens of Figure 12-28: Alternative scenes for different devices
times before, in this opportunity we want our class to be the delegate of
the UIPresentationController object of the view controller that is being
presented, so we must get a reference to this object from the
presentationController property first and then modify its delegate property. In our
example, we can access this property from the instance created by the
showHelp() method. We get the reference first and then assign self to its
delegate property. With the delegate set, we can now implement its
methods.
In our example, we have set the presentation style as formSheet. This is the
standard style for interfaces in the normal state (e.g., iPads in landscape 
mode), but it is automatically adapted to a pageSheet style for interfaces in
the adaptive state (e.g., iPhones in portrait mode). If we want the modal
Do It Yourself: Add a new scene to the Storyboard. Add a button
scene to be shown full screen in the adaptive state, we need to change the
and a label to reproduce the interface in Figure 12-27 (right). Create
style to fullScreen. When the scene is in the adaptive state, the
a new subclass of UIViewController called iPhoneViewController. Select the
UIPresentationController object calls the adaptivePresentationStyle() method to
know what presentation style to use, so we return the fullScreen value and scene, open the Identity Inspector panel, assign the subclass to the
the presentation is set to the style we want. scene, and give it the Storyboard ID "iPhoneView". Connect the
At this point, the presentation style is different for an iPad than an iPhone, Close button to an Action called closeHelp() and complete the class
but the scene shown on the screen is always the same. To specify a with the code in Listing 12-18. Update the PrimaryViewController class
different scene for the adaptive state we implement the with the code in Listing 12-19. Run the application on different
presentationController(UIPresentationController, devices to see how the modal scenes are displayed.
method. This method is called by the
viewControllerForAdaptivePresentationStyle:)
UIPresentationController object on its delegate to know the scene it must
present for each style, so this is our chance to instantiate the scene's view
controller we just added to the interface in Figure 12-27 and return it when
the style is fullScreen (we have identified this scene in the Storyboard with
the string "iPhoneView"). The result is shown in Figure 12-28, below. If we
open the view on an iPad or on a large iPhone in landscape mode, the
scene with the button at the bottom is presented on the screen, but if we
do the same on an iPhone in portrait mode, the scene with the button at
the top is open instead.
Popover Presentation Controller
The UIPopoverPresentationController object can also work with a delegate to

report the state of the popover. The delegate must conform to the
UIPopoverPresentationControllerDelegate protocol, which defines the following
Page Sheets are created from a subclass of the UIPresentationController class.
methods.
This is because the system needs more information to present the sheets,
like the height mode. The same happens with popovers. Popovers are
prepareForPopoverPresentation(UIPopoverPresentationContr
created from a subclass called UIPopoverPresentationController, which provides
all the information necessary to present the popover, such as its margins or
oller)—This method is called to notify the delegate that the popover
the anchor point that determines its position. The following are some of is about to be presented on the screen.
the properties added by the subclass to configure the popover. popoverPresentationControllerShouldDismissPopover(UIPop
overPresentationController)—This method is called to know if the
popoverLayoutMargins—This property sets or returns the margins popover should be dismissed or not.
that define the maximum portion of the screen designated for the popoverPresentationControllerDidDismissPopover(UIPopove
popover. It is a value of type UIEdgeInsets. rPresentationController)—This method is called to notify the
permittedArrowDirections—This property sets or returns a value delegate that the popover was dismissed.
that determines the position of the popover’s arrow. Every popover popoverPresentationController(UIPopoverPresentationContr
contains a little arrow that points to the element that triggered the oller, willRepositionPopoverTo: CGRect, in: UIView)—This
presentation. This property is used to set the position of that arrow. It method is called to notify the delegate that the popover is going to be
is a structure of type UIPopoverArrowDirection with the properties up, repositioned to the rectangle and view determined by the arguments.
down, left, right, any, and unknown.

arrowDirection—This property returns the arrow's direction. It is a The steps to add a popover to the Storyboard are the same as for modal
structure of type UIPopoverArrowDirection with the properties up, down, scenes, with the exception that we must use a specific segue called Present
left, right, any, and unknown. As Popover. Figure 12-29 shows a small scene connected to a button in our
Secondary scene with this type of segue. The purpose of the button is to
sourceView—This property sets or returns the view to which the
open a popover that shows how many calories are provided by 10 units of
popover is anchored. It is an optional value of type UIView.
the item. The button has been connected to a scene with an explicit size of
sourceRect—This property sets or returns a CGRect value with the 300 x 100.
rectangle of the view to which the popover is anchored.
barButtonItem—This property sets or returns the Bar Button Item Figure 12-29: Popover in the Storyboard
to which the popover is anchored. It is an optional value of type
UIBarButtonItem.

be located. For example, if we position the arrow at the top (Up), the scene
is going to be positioned below the element. By default, the segue
activates all the values to let the system decide the most appropriate, but
we can suggest a specific position by activating only the ones we want.
The popover of our example shows a message with the calories provided
by 10 items, so we must pass the selected item from the Secondary scene
to the popover, calculate the calories, and show the result on the screen.
The first step is to modify the prepare() method of the SecondaryViewController
class to send the item's identifier to the popover view controller when the
popover’s segue is triggered. The segue was identified with the
"showPopover" string.

Listing 12-20: Sending the item to the popover
A Present As Popover segue assigns values by default to the 
UIPopoverPresentationController object, but we can modify some of them from override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPicture" {
the Attributes Inspector panel. let controller = segue.destination as! PictureViewController
controller.selected = selected
Figure 12-30: Popover configuration } else if segue.identifier == "showPopover" {
let controller = segue.destination as! PopoverViewController
controller.selected = selected
}
}

The view controller for the popover must calculate the total calories
provided by 10 items and assign the result to the label. We called it
PopoverViewController.

Listing 12-21: Displaying information in a popover



 import UIKit

class PopoverViewController: UIViewController {


The most important option is Directions. These values indicate where the @IBOutlet weak var messageLabel: UILabel!
@IBOutlet weak var caloriesLabel: UILabel!
arrow that points to the element that triggered the presentation is going to
var selected: ItemsData.ID!
override func viewDidLoad() {
super.viewDidLoad() Figure 12-32: Popover with a Left arrow
if selected != nil {
if let item = AppData.selectedCategory?.items.first(where: { $0.id == selected! }) {
messageLabel.text = "10 \(item.name) provide"
caloriesLabel.text = "\(item.calories * 10) Calories"
}
}
}
}

The code in Listing 12-21 defines two Outlets to control the labels, one for
the message and another for the calories. When the view is loaded, we

prepare the strings with the item's name and calories and assign them to
the labels to show them on the screen.
Do It Yourself: Add a button with the title "Show" below the labels
in the Secondary scene. Add a new scene to the Storyboard and
Figure 12-31: Popover
connect the button to this scene with a Present As Popover segue.
Give the segue the identifier "showPopover". Add two labels to the
popover, as shown in Figure 12-29. Select this scene and modify its
explicit size from the Attributes Inspector panel (Figure 12-23). You
can also modify the size in the Storyboard from the Size Inspector
panel (Figure 7-14). Create a subclass of UIViewController called
PopoverViewController and assign it to the new scene. Complete the
subclass with the code in Listing 12-21. Connect the labels to the
messageLabel and caloriesLabel Outlets. Update the prepare() method in
 the SecondaryViewController class with the code in Listing 12-20. Run the
application in the iPad simulator, select an item, and press the Show
If we leave the segue with the configuration by default, the system decides button. You should see something like Figure 12-31. Select the
where to position the arrow and the scene for us. In this case, it decided popover segue, go to the Attributes Inspector panel, and deactivate
that it was better to present the scene at the top of the Show button, but the directions Up, Down, and Right. Run the application again. You
we can select a different position by deactivating the directions we don't
should see something like Figure 12-32.
want. For instance, the following is what we see if only the Left arrow is
activated.

This works fine on iPads. The scene is presented as a popover, and it can be override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPicture" {
dismissed tapping anywhere else on the screen. But in a compact let controller = segue.destination as! PictureViewController
horizontal Size Class, the popover is shown as a Page Sheet and the only controller.selected = selected
} else if segue.identifier == "showPopover" {
way for the user to close it is to drag it down. To solve this problem, we can let controller = segue.destination as! PopoverViewController
implement the protocol methods we used before for modal scenes. One controller.selected = selected
alternative is to return the none value from the adaptivePresentationStyle() controller.popoverPresentationController?.delegate = self
}
method. This tells the UIPresentationController object that we want the scene }
to be presented with its original presentation style, so the scene always func adaptivePresentationStyle(for controller: UIPresentationController) ->
UIModalPresentationStyle {
looks like a popover, no matter the Size Class. return .none
UIKit defines a special protocol for popovers called }
UIPopoverPresentationControllerDelegate. This protocol inherits from the }

UIAdaptivePresentationControllerDelegate protocol, so all the methods defined in
that protocol are also available for popovers. The following are the The UIViewController class includes the popoverPresentationController property to
modifications we need to introduce to the SecondaryViewController class to access the UIPopoverPresentationController object in charge of presenting the
conform to this protocol and modify the popover's adaptive style.
popover. From this object, we assign the SecondaryViewController class as the
popover's delegate and then implement the adaptivePresentationStyle() method
Listing 12-22: Modifying the adaptive style

to return the value none. From now on, every time the popover is about to
import UIKit
be presented in a compact horizontal Size Class, the
UIPopoverPresentationController object receives the none value and therefore
class SecondaryViewController: UIViewController, UIPopoverPresentationControllerDelegate {
@IBOutlet weak var itemThumbnail: UIImageView! does not adapt the presentation style (the popover is not shown as a Page
@IBOutlet weak var itemName: UILabel! Sheet). Figure 12-33 shows what the popover looks like on an iPhone in
@IBOutlet weak var itemCalories: UILabel!
var selected: ItemsData.ID! portrait mode.

override func viewDidLoad() {


super.viewDidLoad() Figure 12-33: Popover on an iPhone in portrait mode
if selected == nil {
if let item = AppData.selectedCategory.items.first {
selected = item.id
}
}
if let item = AppData.selectedCategory?.items.first(where: { $0.id == selected! }) {
itemThumbnail?.image = UIImage(named: item.image)
itemName?.text = item.name
itemCalories?.text = "Calories: \(item.calories)"
}
}
CHAPTER 13 - ALERT VIEWS

Do It Yourself: Update the SecondaryViewController class with the code


in Listing 12-22. Run the application on the iPhone simulator, select
an item and press the Show button to open the popover.

13.1 Alert Views addAction(UIAlertAction)—This method adds a new action to the


scene. Actions are objects that create buttons for the scene with

specific configurations and purposes.
Alert views are predefined modal scenes that can display messages and addTextField(configurationHandler: Block)—This method adds a
receive input from the user. Their purpose is to deliver important Text Field to the scene. The argument is a closure that provides the
information that requires immediate attention. For example, an Alert View configuration for the Text Field.
may be used to ask the user for confirmation before deleting a file or data
from the model. They may include a title, a message, Text Fields, and Alert Views present buttons along with the text to receive input from the
buttons, and be presented full screen or as popovers on some devices. user. The buttons are created from the UIAlertAction class. The class includes
UIKit includes a subclass of the UIViewController class called UIAlertController to the following initializer and properties.
define these views, but there is no option to add them to the Storyboard,
they ought to be created from code with the following initializer. UIAlertAction(title: String?, style: Style, handler: Block)—This
initializer creates a UIAlertAction object configured with the values
UIAlertController(title: String?, message: String?, assigned to the arguments. The title argument is the title for the
preferredStyle: Style)—This initializer creates a UIAlertController button. The style argument defines the purpose of the button. The
object configured with the values assigned to the arguments. The title possible values are default (for a generic type of button), cancel (for a
argument is the text displayed at the top, the message argument is button that cancels the request), and destructive (for a button that
the message displayed below the title, and the preferredStyle performs a destructive task, such as deleting a file). The handler
argument is an enumeration with the values alert and actionSheet, to argument is a closure that is executed when the button is pressed.
indicate the scene's type (Alert or Action Sheet).
title—This property returns a string with the title of the button.
The class also includes properties and methods to add and configure the style—This property returns the button's type. It is an enumeration
scene’s elements. called Style with the values default, cancel, and destructive.
isEnabled—This property sets or returns a Boolean value that
title—This property sets or returns the title. determines whether the button is enabled or not.
message—This property sets or returns the message
actions—This property returns an array with references to the There are two types of Alert Views: Alerts and Action Sheets. Alert Views of
type Alert are presented at the center of the screen, while those of type
actions (buttons) in the scene.
Action Sheet are anchored to the bottom of the screen in small devices or
textFields—This property returns an array with references to the presented as popovers in devices with larger screens. They may include
Text Fields in the scene. messages and buttons, but only the Alert types can include Text Fields.
Alerts

Alert Views of type Alert are usually presented to inform the user that a
specific task has been completed or that something went wrong. For
example, we may show an Alert View of type Alert to report an error when
the user tries to save the value of an empty Text Field. The following
interface includes a Text Field and a button to allow the user to enter a
name. If the user presses the button before inserting any text, the code
displays an Alert View reminding the user that a name is required.

Figure 13-1: Interface to test Alert Views

The view controller for this scene must check whether the Text Field
contains text or not and show the alert if it is empty.

Listing 13-1: Presenting an Alert View



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var nameText: UITextField!

@IBAction func saveName(_ sender: UIButton) {


var text = nameText.text!
text = text.trimmingCharacters(in: .whitespaces)
if text == "" {
showAlert()

} else {
print("Value stored: \(text)")
nameText.text = ""
}
}
func showAlert() {
let alert = UIAlertController(title: "Error", message: "Insert your name in the field",
preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}

The view controller in Listing 13-1 includes an Outlet for the Text Field
called nameText and an Action for the button called saveName(). When the
user presses the button, the Action checks the value of the text property. If
the value is empty, it calls the showAlert() method to present an Alert View,
otherwise it prints a message on the console and clears the field.
The showAlert() method creates the view controller with the UIAlertController()
initializer, which includes the title, the message, and the view's type (Alert 
in this case). Next, it creates and adds a UIAlertAction object for every button
to be included. The initializer for this class also allows us to define the Do It Yourself: Create a new project. Add a label, a Text Field, and a
information required, like the title for the button, its purpose, and a button to the scene. Connect the Text Field to the view controller
closure that is going to be executed when the button is pressed. For this with an Outlet called nameText and the button to an Action called
example, we need only one button of type Default with no action, so the saveName(). Complete the ViewController class with the code in Listing
handler is declared as nil. The action is added to the Alert View with the 13-1. Run the application and press the button. You should see the
addAction() method and then the scene is presented on the screen with the
alert shown in Figure 13-2.
present() method, as we did for modal scenes in Chapter 12. If we run the
application and press the Save button before typing any text, an Alert View
The OK button in our example doesn't do anything other than dismissing
reminds us that we must enter our name.
the scene. If we want to perform a task, we must provide a closure for the
handler argument of the UIAlertAction initializer. For instance, the following
Figure 13-2: Alert View of type Alert with a single button
code changes the background color of the Text Field after the OK button is
pressed.
Listing 13-2: Changing the background color of the Text Field when the buttons can perform additional tasks, we just need to provide the closure
button is pressed in the UIAlertAction initializer.

func showAlert() { Do It Yourself: Replace the showAlert() method in your project with
let alert = UIAlertController(title: "Error", message: "Insert the name in the field", preferredStyle:
.alert)
the methods in Listings 13-2 and 13-3 to test each example.
let action = UIAlertAction(title: "OK", style: .default, handler: { (action) in
self.nameText.backgroundColor = UIColor(red: 255.0/255.0, green: 230.0/255.0, blue:
Alert Views of type Alert may also include Text Fields. Text Fields are
230.0/255.0, alpha: 1.0)
}) usually added to an Alert View to get input from the user or to create sign-
alert.addAction(action) up or log-in forms. For instance, we can create an interface with an initial
present(alert, animated: true, completion: nil)
} scene that allows the user to log in, and a second scene that opens if the
 values inserted by the user match the data in the model. The interface
below includes a welcoming window with a button called Log In to open an
We can include more buttons in the Alert View by adding more actions. Alert View to insert an email and a password.
The following example asks the user if the application should store the
value anyway and offers a Cancel button to cancel the operation. Figure 13-3: Interface to log in

Listing 13-3: Adding a Cancel button



func showAlert() {
let alert = UIAlertController(title: "The field is empty", message: "Are you sure do you want to
store an empty string?", preferredStyle: .alert)
let action = UIAlertAction(title: "Yes", style: .default, handler: { (action) in
print("Value stored")
})
alert.addAction(action) 
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(cancel)
present(alert, animated: true, completion: nil) Text Fields are added to the Alert View with the addTextField() method. In our
} example we need two: one for the email and another for the password.

The view controller of the initial scene must include an Action for the Log
In button to create the Alert View.
The UIAlertController object places the buttons inside the scene in the order
they were declared in code. We may add all the buttons we need, except
Listing 13-4: Creating and processing a login form
for buttons of type Cancel, of which only one is permitted per Alert View.

Because we did not include a closure for the handler argument of the
import UIKit
Cancel button, the function of the button is just to dismiss the view, but all

class ViewController: UIViewController { order of the Text Fields in the code (the first one is at index 0, the next one
@IBAction func loginUser(_ sender: UIButton) {
let alert = UIAlertController(title: "Insert Email and Password", message: nil, preferredStyle: at index 1, and so on). For this example, we keep it simple and compare the
.alert) values of each Text Field with hard-coded strings. If the values match, we
perform the segue to open the second scene where the user can begin
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(cancel) working with our app (the segue is a Show segue identified with the string
"showMainScreen").
let action = UIAlertAction(title: "Login", style: .default, handler: { (action) in
if let fields = alert.textFields {
let email = fields[0].text Figure 13-4: Alert View with Text Fields
let password = fields[1].text

if email == "[email protected]" && password == "12345" {


self.performSegue(withIdentifier: "showMainScreen", sender: self)
}
}
})
alert.addAction(action)

alert.addTextField(configurationHandler: { (textField) in
textField.placeholder = "Email"

})
alert.addTextField(configurationHandler: { (textField) in
textField.placeholder = "Password" Do It Yourself: Create a new project. Embed the scene in a
textField.isSecureTextEntry = true
}) Navigation Controller. Add a button with the title Log In to the scene.
present(alert, animated: true, completion: nil)
}
Add a second scene to the Storyboard. Add a label with the text "You
} are logged in" to the second scene. Connect the first scene to the

second scene with a Show segue. Give the segue the identifier
“showMainScreen”. Connect the Log In button with an Action called
The addTextField() method includes an argument that takes a closure to
loginUser(). Complete the Action with the code in Listing 13-4. Run the
configure the Text Field. For the email, we only need a placeholder, but the
Text Field for the password should be declared as secure with the application and press the button. Insert the email "[email protected]"
isSecureTextEntry property, so the password is not displayed on the screen. and the password "12345". Press Login. You should see something
The Alert View includes two buttons: Cancel and Login. When the user like Figure 13-4.
presses the Login button, we compare the values inserted in the fields with
the data we have in our model (usually a database in the device or in a
server). To access the Text Fields from inside the closure, we read the
textFields property of the Alert View. This property contains an array with
references to the Text Fields available. The indexes of the array follow the
Action Sheets the cancel button is always displayed at the bottom, in a separate box, and
the destructive button is highlighted in red.

Figure 13-5: Action Sheet
Action Sheets are generally presented when the user must decide between
multiple available options. As shown in the following example, the creation
of Action Sheets is like Alert Views, but the initializer must be defined with
the style actionSheet. The example assumes that the interface contains a
button connected to an Action called openSheet().

Listing 13-5: Creating an Action Sheet



import UIKit

class ViewController: UIViewController {


@IBAction func openSheet(_ sender: UIButton) {
let alert = UIAlertController(title: "Emails", message: "What do you want to do with the
message?", preferredStyle: .actionSheet)

let action1 = UIAlertAction(title: "Move to Inbox", style: .default, handler: nil)


alert.addAction(action1)
let action2 = UIAlertAction(title: "Delete", style: .destructive, handler: nil)
alert.addAction(action2)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 
alert.addAction(cancel)

present(alert, animated: true, completion: nil) Do It Yourself: Create a new project. Add a button called Open
} Sheet to the scene. Connect the button to the ViewController class with
}
 an Action called openSheet(). Complete the Action with the code in
Listing 13-5. Run the application on an iPhone simulator and press
The code in Listing 13-5 creates an Alert View of type Action Sheet when the button. You should see something like Figure 13-5.
the user presses the button on the interface. This Action Sheet contains a
total of three buttons: a default button called Move to Inbox, a destructive As we already mentioned, Action Sheets are presented as modal scenes at
button called Delete, and a cancel button called Cancel. We didn't include the bottom of the screen in small devices, but as popovers in iPads. The
any handler for the buttons, so their only purpose is to dismiss the scene, problem is that the system cannot display a popover without the necessary
but the example illustrates how an Action Sheet is created and how the information. If we execute the previous application on an iPad, we get an
buttons are organized. No matter the position of the buttons in the code, error and the application crashes. To make sure that the code works on

every device, we must configure the presentation controller. For this the internal frame of the button’s view to the sourceRect property we make
purpose, the UIAlertController object includes the popoverPresentationController sure that the arrow always points to one side of the button and the button
property with a reference to the UIPopoverPresentationController object in is not covered by the view. Finally, we set the permittedArrowDirections
charge of the presentation when the presentation style is popover. The property with the value up, so the scene is positioned below the button.
values we need to provide are the view to which the Action Sheet is The Action Sheet adapts the presentation style to the space available and
anchored, the rectangle inside the view the popover’s arrow points to, and also the design. If we run the example in Listing 13-6 on an iPad, the Cancel
the direction of the arrow. button is automatically removed because the user can dismiss the popover
by tapping anywhere else on the screen.
Listing 13-6: Presenting the Action Sheet as a popover in iPads
 Figure 13-6: Action Sheet on an iPad
import UIKit

class ViewController: UIViewController {


@IBAction func openSheet(_ sender: UIButton) {
let alert = UIAlertController(title: "Emails", message: "What do you want to do with the
message?", preferredStyle: .actionSheet)

if let popover = alert.popoverPresentationController {


popover.sourceView = sender
popover.sourceRect = sender.bounds
popover.permittedArrowDirections = [.up]
}
let action1 = UIAlertAction(title: "Move to Inbox", style: .default, handler: nil)
alert.addAction(action1)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

alert.addAction(cancel)
let action2 = UIAlertAction(title: "Delete", style: .destructive, handler: nil)
alert.addAction(action2)
Do It Yourself: Update the ViewController class with the code in
present(alert, animated: true, completion: nil) Listing 13-6. Run the application on an iPad simulator and press the
}
}
button. You should see something like Figure 13-6.

The view controller in Listing 13-6 adds the configuration for the popover
to the previous example. We decided to anchor the popover to the button
that opens the Alert View, so we assign the reference received by the
Action to the sourceView property of the UIPopoverPresentationController object.
This anchors the popover to the button, but we still must determine to
which part of the button’s view the arrow is going to point to. By assigning
CHAPTER 14 - CONCURRENCY 14.1 Asynchronous and Concurrent Tasks

Apple systems can take advantage of the multiple cores in modern


processors to execute different 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.
Units of code that are prepared to run in parallel are called Tasks. In Swift,
tasks can be implemented through 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.

Figure 14-1: Asynchronous and concurrent programming

Because multiple applications can run at the same time, the system doesn't
allocate certain number of cores per application. What it does is to create
execution threads, assign the tasks to the threads, and then decide which

threads are going to be executed by which core depending on the available Tasks
resources. In the example of Figure 14-1, left, there is an asynchronous task

that loads and image from the Web and then displays it on the screen.
While waiting for the image to download, the thread is free to perform
Asynchronous and concurrent code is defined by tasks. The Swift Standard
other tasks, so the system may use it to execute a different task that
Library includes the Task structure to create and manage these tasks. The
updates the progress bar. On the right, the tasks were created as
following is the structure's initializer.
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.

The Task structure includes the following properties to cancel a task.

isCancelled—This property returns a Boolean value that indicates if


the task was cancelled.
cancel()—This method cancels 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.

currentPriority—This property returns the priority of the current


task. It is a TaskPriority structure with the properties background, high, low,
medium, userInitiated, and utility.

isCancelled—This property returns a Boolean value that indicates


whether the current task was cancelled.
sleep(UInt64)—This method suspends the current task the time Async and Await
specified by the argument in nanoseconds.

Asynchronous and concurrent tasks are defined in Swift with the async and
await keywords. For instance, to create an asynchronous task, we mark a
method with async and then wait for that method to complete with await.
This means that an asynchronous method can only be called with the await
keyword from inside another asynchronous method, which creates an
indefinite cycle. To start the cycle, we can create an asynchronous task with
the Task structure, as shown next.
Listing 14-1: Creating an asynchronous task

import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
Task(priority: .background) {
let imageName = await loadImage(name: "image1")
print(imageName)
}
}
func loadImage(name: String) async -> String {
await Task.sleep(3 * 1000000000)
return "Name: \(name)"
}
}

The Task structure in this example is created with a background priority,


which means that the task is not going to have priority over other parallel
tasks. In the closure, we called the loadImage() method and then print the
value returned. This is a method we defined 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 name received from the task. 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
The processes are executed one by one, in sequential order. The task waits
must wait for this process to be over.
for a process to be over before executing the next. In this case, the whole
The application creates the task and adds it to the thread. When the
task is going to take 9 seconds to finish (3 seconds per process). The more
system is ready to run it, it executes the closure. In the closure, we call the
processes we add to an asynchronous task, the more is going to take to
loadImage() method and wait for its completion. The method pauses for 3
finish. For this reason, sometimes we might need to cancel a task before it
seconds and then returns a string. After this, the task continues executing
is completed.
the statements, and a message is printed on the console.
Cancelling a task is easy. We must assign the task to a variable and then call
the cancel() method on it. But the processes are not automatically cancelled;
Do It Yourself: Create a new project. Update the ViewController class we must detect whether the task has been cancelled with the isCancelled
with the code in Listing 14-1. Run the application. You should see a property and stop the process ourselves.
message appear on the console after 3 seconds. Use this project to
try the rest of the examples. Listing 14-3: Cancelling a task

A task can perform multiple asynchronous processes. For instance, in the import UIKit
following example we call the loadImage() method three times to download
class ViewController: UIViewController {
three images. override func viewDidLoad() {
super.viewDidLoad()
let myTask = Task(priority: .background) {
Listing 14-2: Running multiple asynchronous tasks let imageName = await loadImage(name: "image1")
 print(imageName)
import UIKit }
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) in
class ViewController: UIViewController { print("The time is up")
override func viewDidLoad() { myTask.cancel()
}
super.viewDidLoad()
Task(priority: .background) { }
let imageName1 = await loadImage(name: "image1") func loadImage(name: String) async -> String {
await Task.sleep(3 * 1000000000)
let imageName2 = await loadImage(name: "image2")
let imageName3 = await loadImage(name: "image3") if !Task.isCancelled {
print("\(imageName1), \(imageName2), and \(imageName3)") return "Name: \(name)"
} else {
}
} return "Task Cancelled"
func loadImage(name: String) async -> String { }
}
await Task.sleep(3 * 1000000000)
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()
method, we read the isCancelled property and respond accordingly. If the Because we need to wait for the task to finish before using the value, we
task was cancelled, we return the "Task Cancelled" message, otherwise, have defined a second task. The process starts as always, with a task that
the name is returned as before. Notice that in this case we are working calls the loadImage() method, but now we create a second task in this
inside a process executed by the task, so we use the type property instead method that returns a string. This task executes another asynchronous
of the instance property (we read the isCancelled property from the data method that waits for 3 seconds and returns the number 50000. After this
type, not the instance). This property returns true or false depending on the process is over, the task creates a string with the name and the number
state of the current task. As a result, the task is cancelled before it is and returns it. We then get the string from the value property, and return it
completed. to the original task, which prints it on the console.
Tasks can receive and return values. The Task structure includes the value So far, we have worked with asynchronous methods, but we can also
property to provide access to the value returned by the task. Of course, we define asynchronous properties. All we need to do is to define the getter
also need to wait for the task to complete before reading this value, as with the async keyword, as shown next.
shown next.
Listing 14-5: Defining and asynchronous properties
Listing 14-4: Reading a value returned by a task 
 import UIKit
import UIKit
class ViewController: UIViewController {
class ViewController: UIViewController { var thumbnail: String {
override func viewDidLoad() { get async {
super.viewDidLoad() await Task.sleep(3 * 1000000000)
Task(priority: .background) { return "mythumbnail"
let imageName = await loadImage(name: "image1") }
print(imageName) }
} override func viewDidLoad() {
} super.viewDidLoad()
func loadImage(name: String) async -> String { Task(priority: .background) {
let result = Task(priority: .background) { () -> String in let imageName = await thumbnail
let imageData = await getMetadata() print(imageName)
return "Name: \(name) Size: \(imageData)" }
} }
let message = await result.value }
return message 
}
func getMetadata() async -> Int { This time, instead of calling a method, the task reads a property. The
await Task.sleep(3 * 1000000000)
return 50000 property suspends the tasks for 3 seconds and returns a string. Again, we

are suspending the task for didactic purposes, but we can perform any Errors
demanding task we want in this property, such as downloading an image

from the Web or processing data.
Asynchronous tasks are not always successful, so we must be prepared to
process the errors returned. If we are creating our own tasks, we can
define the errors with an enumeration that conforms to the Error protocol,
as explain in Chapter 3 (see Listing 3-190). The following example defines a
structure with two errors, one to return when no metadata is found on the
server (noData), and another for when the image is not available (noImage).

Listing 14-6: Responding to errors



import UIKit

enum MyErrors: Error {


case noData, noImage
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

Task(priority: .background) {
do {
let imageName = try await loadImage(name: "image1")
print(imageName)
} catch MyErrors.noData {
print("Error: No Data Available")
} catch MyErrors.noImage {
print("Error: No Image Available")
}
}
}
func loadImage(name: String) async throws -> String {
await Task.sleep(3 * 1000000000)

let error = true


if error {
throw MyErrors.noImage
}
return "Name: \(name)"
}
} Concurrency


The loadImage() method in this example always throws a noImage error to test
the code. The task checks for errors with a do catch statement and prints a Asynchronous tasks are useful when we want to free resources for the
message on the console to report the result. Notice that when an system to perform other tasks, like updating the interface, but when we
asynchronous function can throw errors, we must declare the throws want to run two tasks simultaneously, we need concurrency. For this
keyword after async, as shown in Listing 14-6. purpose, the Swift Standard Library defines the async let statement. To turn
an asynchronous task into multiple concurrent tasks, all we need to do is to
declare the processes with the async let statement, as shown next.

Listing 14-7: Defining a concurrent task



import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
let currentTime = Date()

Task(priority: .background) {
async let imageName1 = loadImage(name: "image1")
async let imageName2 = loadImage(name: "image2")
async let imageName3 = loadImage(name: "image3")

let listNames = await "\(imageName1), \(imageName2), and \(imageName3)"


print(listNames)
print("Total Time: \(Date().timeIntervalSince(currentTime))")
}
}
func loadImage(name: String) async -> String {
await Task.sleep(3 * 1000000000)
return "Name: \(name)"
}
}

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 14-7, we create three concurrent tasks

(imageName1, imageName2, and imageName3). The process is the same Actors


as before, they call the loadImage() method, the method pauses the task for

3 seconds, and returns a string. But because this time the processes run in
parallel, the time they take to complete is around 3 seconds (not 9
When working with concurrent tasks, we could run into a problem called
seconds, as in the example of Listing 14-2). Notice that to make sure the
data race. A data race happens when two or more tasks running in parallel
tasks are running concurrently we calculate the seconds they take to
try to access the same data. For instance, they try to modify the value of a
complete with a Date structure and print the interval on the console.
property at the same time. This could lead to errors or serious bugs. To
solve this issue, the Swift Standard Library includes actors.
Actors are data types that isolate parallel tasks from one another, so when
a task is modifying the values of an actor, other tasks are forced to wait.
Actors are reference types and are defined like classes, but instead of the
class keyword, they are declared with the actor keyword. Another important
difference with classes is that the properties and methods must be
accessed asynchronously (we must wait with the await keyword). This
ensures that the code can wait until the actor is free to respond (no other
task is accessing the actor).
The following example illustrates how actors work. The code declares an
actor with two properties and a method, creates an instance, and then
calls the method from multiple tasks.

Listing 14-8: Defining an actor



import UIKit

actor ItemData {
var name: String
var counter: Int

init(name: String) {
self.name = name
self.counter = 0
}
func changeName(newName: String) {
name = newName
counter += 1
}
} Main Actor
class ViewController: UIViewController {
var item: ItemData! 
override func viewDidLoad() {
super.viewDidLoad() As we already mentioned, tasks are assigned to execution threads and then
item = ItemData(name: "Undefined")
the system distributes these threads among the multiple cores of a
Task(priority: .background) { processor to perform the tasks as fast and smoothly as possible. A thread
async let imageName1 = loadImage(name: "potatos")
async let imageName2 = loadImage(name: "milk")
can manage multiple tasks, and multiple threads may be created for our
async let imageName3 = loadImage(name: "orange") application. Besides the threads initialized to process asynchronous and
concurrent tasks, the system always creates a thread, called main thread,
let listNames = await "\(imageName1), \(imageName2), and \(imageName3)"
print(listNames) to start the application and run non-asynchronous codes, including the
} code that creates and updates the interface. This means that if we try to
}
func loadImage(name: String) async -> String {
modify the interface from an asynchronous or concurrent task, we may
await item.changeName(newName: name) 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
let count = await item.counter
return "\(name) \(count)" by the system that makes sure that every task that wants to interact with
} the main code 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
The method in the ItemData actor updates the value of the name property Actor (the main thread): the @MainActor modifier and the run() method.
and then increments the value of the counter property by 1 to keep a count With the @MainActor modifier we can mark an entire method to run on the
on the number of times the name was modified. When the view is loaded, main thread, while the run() method executes a closure in the main thread.
we create an instance of this actor with the name "Undefined" and then For instance, in the following example we mark the loadImage() method with
define a task with three concurrent tasks inside. The concurrent tasks call @MainActor to make sure that the code inside the method is executed in the
the loadImage() method. In this method we call the changeName() method to main thread and we are able to modify the value of a label.
modify the name in the actor and then return this value along with the
current value of the counter property. Notice that we wait for both, the Listing 14-9: Executing a method in the Main Actor
method and the property, to make sure the tasks don't overlap. 
import UIKit

class ViewController: UIViewController {


@IBOutlet weak var myLabel: UILabel!

override func viewDidLoad() {


super.viewDidLoad()

Task(priority: .background) { }
await loadImage(name: "image1") }
} func loadImage(name: String) async {
} await MainActor.run {
@MainActor func loadImage(name: String) async { myLabel.text = name
myLabel.text = name }
print(name) print(name)
} }
} }
 

This code creates an asynchronous task as before, but now the method is This code is the same as before, but now only the statement that modifies
marked with @MainActor, so the code is executed in the main thread, and the interface runs in the main thread, while the rest of the statements in
we can safely assign a text to the label. the method keep running in the thread assigned to the task. Notice that
the run() method is marked with await. The await keyword is necessary
Do It Yourself: Update the ViewController class with the code in because the method may have to wait for the main thread to be free to
Listing 14-9. Add a label to the scene and connect it with the myLabel execute the statements.
Outlet. Run the application. You should see the text "image1" on the The run() method can also return a value. This is useful when we need to
screen. get information from the interface. In the following example, we get the
label's current text and print it on the console.
Most of the time, only part of our code deals with the interface, but the
rest can be executed in the current thread. For cases like this, we can Listing 14-11: Returning a value from the Main Actor

implement the run() method. This is a type method defined by the MainActor
import UIKit
structure (the structure used to create the Main Actor). The method takes
a closure with the statements we need to execute in the main thread. class ViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel!

Listing 14-10: Executing code in the Main Actor override func viewDidLoad() {
 super.viewDidLoad()
import UIKit
Task(priority: .background) {
await loadImage(name: "image1")
class ViewController: UIViewController {
}
@IBOutlet weak var myLabel: UILabel!
}
func loadImage(name: String) async {
override func viewDidLoad() {
let labelText: String = await MainActor.run {
super.viewDidLoad()
let text = myLabel.text
return text ?? ""
Task(priority: .background) {
}
await loadImage(name: "image1")
print(labelText)
} Asynchronous Sequences
}
 

Sometimes, information may be returned as a sequence of values, but the


values may not be available all at once. In cases like this, we can create an
asynchronous sequence. This sequence is like an array, but the values are
returned asynchronously, so we must wait for each value to be ready.
The Swift Standard Library includes two protocols to create asynchronous
sequences: the AsyncSequence protocol to define the sequence and the
AsyncIteratorProtocol protocol to define the code that iterates through the
sequence to return the values. The AsyncSequence protocol requires the data
type to include a typealias with the name Element that represents the data
type returned by the sequence, and the following method.

makeAsyncIterator()—This method returns the instance of the


iterator in charge of producing the values. The value returned is an
instance of a data type that conforms to the AsyncIteratorProtocol
protocol.

On the other hand, the AsyncIteratorProtocol protocol only requires the data
type to implement the following method.

next()—This method returns the next element on the list. The


method is called over and over again until the value returned is nil,
which indicates the end of the sequence.

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
returned by the sequence and initialize the iterator, and another that
conforms to the AsyncIteratorProtocol protocol to produce the values. In the
following example, we define an asynchronous sequence that processes an
array of strings one by one and returns a sequence of String values.

The code in Listing 14-12 simulates the process of asynchronously


Listing 14-12: Defining an asynchronous sequence downloading images from the Web. The iterator with the next() method is
 defined first. In this method, we read the strings from the imageList array
import UIKit and update a counter to know when we have reached the end (the value of
the counter is equal or greater than the number of elements in the array).
struct ImageIterator : AsyncIteratorProtocol {
let imageList: [String] Notice that we also wait for 3 seconds to pretend that we are downloading
var current = 0 the image from the Web.
mutating func next() async -> String? { The asynchronous sequence is defined next by the ImageLoader structure.
guard current < imageList.count else { The structure includes a typealias called Element to indicate that the
return nil
}
sequence returns String values, and the makeAsyncIterator() method to
await Task.sleep(3 * 1000000000) initialize the iterator. Now everything is ready to read the values in the
sequence, so we start a task, create an instance of the ImageLoader
let image = imageList[current]
current += 1 sequence, and then iterate through the elements with a for in loop. Notice
return image that the for in loop requires the await keyword to wait for each element of
}
}
the sequence. The loop runs until the value returned by the iterator is nil.
struct ImageLoader : AsyncSequence {
typealias Element = String
let imageList: [String]
Do It Yourself: Update the ViewController class with the code in
Listing 14-12. Remember to delete the label from the scene if you
func makeAsyncIterator() -> ImageIterator {
return AsyncIterator(imageList: imageList)
don't use it anymore. Run the application. You should see the values
} in the imageList array printed on the console every 3 seconds.
}
class ViewController: UIViewController {
var imageList = ["image1", "image2", "image3"]

override func viewDidLoad() {


super.viewDidLoad()

Task(priority: .background) {
let loader = ImageLoader(imageList: imageList)
for await image in loader {
print(image)
}
}
}
}

Task Group addTask(priority: TaskPriority?, operation: Closure)—This
method adds a task to the group. The priority argument is a structure

that helps the system decide when to execute the task. The structure
A task group is a container for dynamically generated tasks. Once the group includes type properties to defined standard priorities. The currently
is created, we can add and manage tasks from code as required by the available are background, high, low, medium, userInitiated, and utility. And the
application. The Swift Standard Library defines the following global operation argument is a closure with the statements to be executed
methods to create a group. by the task.
cancelAll()—This method cancels all the tasks in the group.
withTaskGroup(of: Type, returning: Type, body: Closure)—This
method creates a task group. The of argument defines the data type A task group is an asynchronous sequence of tasks. This sequence is
returned by the tasks, the returning argument defines the data type generic, which means that the tasks, and the group, can return any types
returned by the group, and the body argument is the closure where of values. This is the reason why the methods to create a task group have
the tasks are defined. If no values are returned, the arguments may two arguments to specify the data types returned by the tasks and the
be ignored. group. These two methods are the same. The one we implement depends
on whether we want to throw errors or not. These methods create a
withThrowingTaskGroup(of: Type, returning: Type, body:
TaskGroup structure that works with the data types specified by the
Closure)—This method creates a task group that can throw errors. arguments and send the instance to the closure. Using this value inside the
The of argument defines the data type returned by the tasks, the closure, we can add to the group all the tasks we want, as shown next.
returning argument defines the data type returned by the group, and
the body argument is the closure where the tasks are defined. If no Listing 14-13: Defining a task group
values are returned, the arguments may be ignored. 
import UIKit
The group is defined by an instance of the TaskGroup structure, which
class ViewController: UIViewController {
includes properties and methods to manage the tasks in the group. The override func viewDidLoad() {
following are the most frequently used. super.viewDidLoad()
Task(priority: .background) {
await withTaskGroup(of: String.self) { group in
isCancelled—This property returns a Boolean value that indicates group.addTask(priority: .background) {
let imageName = await self.loadImage(name: "image1")
whether the group was cancelled. return imageName
}
isEmpty—This property returns a Boolean value that indicates group.addTask(priority: .background) {
whether the group has any remaining tasks. let imageName = await self.loadImage(name: "image2")
return imageName
}

group.addTask(priority: .background) {
let imageName = await self.loadImage(name: "image3") CHAPTER 15 - STORAGE
return imageName
}
for await result in group {
print(result)
}
}
}
}
func loadImage(name: String) async -> String {
await Task.sleep(3 * 1000000000)
return "Name: \(name)"
}
}

In this example, we create a task group that doesn't throw errors. The
group doesn't return a value either, but the tasks return a string, so we
declare the of argument of the withTaskGroup() method with a reference to
the String data type (String.self). The tasks are added to the group one after
another. Each task performs the same process as before. They call the
loadImage() method asynchronously and get a string in return.
Because a task group is an asynchronous sequence of tasks, we can iterate
though the values with a for in loop, as we did for the asynchronous
sequence created in the previous section of this chapter. Every time a task
is completed, the group returns the value produced by the task until no
tasks remain, in which case the value nil is returned to finish the loop.

IMPORTANT: Task Groups store the tasks in sequence. You can


remove, filter, or even check whether a group contains a specific
task. The topic is beyond the scope of this book. For more
information, visit our website and follow the links for this chapter.
15.1 User Preferences User Defaults
 

Up to this point, all the data was stored in arrays created in the model, and The simplest system available on Apple devices is called Users Defaults.
the values were hard-coded, meaning they are always the same every time This system was designed to store user preferences, which may include
the app is launched. If we added a new value to the model, it was only values set by the user to determine how the app should work and values
preserved for as long as the app was running, but as soon as the app was set by the app to restore previous states. These values are stored in a
closed, the values were lost, and everything was back to the initial state. To system-managed database and therefore continue to exist after the
preserve the values and all the changes introduced by the user, we need to application is closed and for as long as the user or the application decides
store the data permanently on the device. Apple includes several systems to keep them.
for storing data. They all work with files but can take various forms, from The Foundation framework defines a class called UserDefaults to manage this
simple text files to databases (indexed data). system. Only one UserDefaults object is assigned per application, so we can
reference this object anywhere in the code and always process the same
values. The class offers the following type property to get a reference to
the UserDefaults object created for the app.

standard—This type property returns a reference to the app’s


UserDefaults object.

The values are assigned to the UserDefaults object with an associated key
that we can use to retrieve them later. The types of values we can work
with are restricted to Property List values (NSNumber, NSString, NSDate,
NSArray, NSDictionary, NSData, and the equivalents in Swift), but we can also
work with other types of values, including custom objects, by converting
them into NSData objects, as we will see in further chapters. The UserDefaults
class includes the following methods to store, retrieve, and delete values of
any of these types.

set(Any?, forKey: String)—This method stores the value specified


by the first argument with the key specified by the forKey argument.
If the value already exists, it is updated.

object(forKey: String)—This method retrieves the value associated we can use the User Defaults system to allow the user to store a limit on
with the key specified by the forKey argument. If the value does not the number of items managed by the application, and then set that limit
exist, it returns nil. back every time the app is executed. The interface below includes a
Stepper and a label to illustrate how the process works.
removeObject(forKey: String)—This method removes the value
associated with the key specified by the forKey argument.
Figure 15-1: Interface to store settings

Because the methods above process values of any type, they return them
as objects of type Any. This means that we always need to cast the values
to the right data type before using them. To simplify our work, the class
includes methods to retrieve the values for specific types.

bool(forKey: String)—This method retrieves a value of type Bool. If
there is no value set for the key, the method returns the value false. The view controller for this scene must update the value of the label when
the interface is shown on the screen and store it in the User Defaults
float(forKey: String)—This method retrieves a value of type Float. If
system every time the user sets a new one.
there is no value set for the key, the method returns the value 0.0.
integer(forKey: String)—This method retrieves a value of type Int. If Listing 15-1: Storing values in the User Defaults system
there is no value set for the key, the method returns the value 0. 

double(forKey: String)—This method retrieves a value of type import UIKit

Double.If there is no value set for the key, the method returns the class ViewController: UIViewController {
value 0.0. @IBOutlet weak var counter: UIStepper!
@IBOutlet weak var counterLabel: UILabel!
string(forKey: String)—This method retrieves a value of type String. var defaultValues: UserDefaults!

array(forKey: String)—This method retrieves an array. override func viewDidLoad() {


super.viewDidLoad()
dictionary(forKey: String)—This method retrieves a dictionary. defaultValues = UserDefaults.standard
if let number = defaultValues.object(forKey: "counter") as? Double {
data(forKey: String)—This method retrieves a value of type Data. counter.value = number
counterLabel.text = String(number)
url(forKey: String)—This method retrieves a value of type URL. }
}
The User Defaults system can store any amount of data we want, but it is @IBAction func incrementValue(_ sender: UIStepper) {
let current = counter.value
recommendable to use it to store short strings and small values. Its main defaultValues.set(current, forKey: "counter")
purpose is to serve as a storage system for the app’s settings. For instance, counterLabel.text = String(current)
}
} the main view is loaded, compares it with the current date, and prints the

difference on the console. At the end, we update the value with the
current date for the next cycle.
The way we store the value is simple. We get a reference to the UserDefaults
object of our application and then call any of the methods provided by the
Listing 15-2: Storing app values
object to this effect. In Listing 15-1, we declare a property called defaultValues

to store a reference to the object and use the object() and set() methods to
import UIKit
read and store a value with the "counter" key. The object() method returns
an optional of type AnyObject, so we unwrap the value and cast it to the class ViewController: UIViewController {
original data type (in this case a Double). If there is already a value stored @IBOutlet weak var counter: UIStepper!
@IBOutlet weak var counterLabel: UILabel!
with the "counter" key, we assign it to the value property of the Stepper to var defaultValues: UserDefaults!
start counting from that number and to the label to show it on the screen.
override func viewDidLoad() {
The Stepper is connected to an Action called incrementValue(). When the user super.viewDidLoad()
presses the buttons, this method gets the current value, stores it in the defaultValues = UserDefaults.standard
if let number = defaultValues.object(forKey: "counter") as? Double {
User Defaults system with the "counter" key, and updates the label. In counter.value = number
consequence, the label always displays the current value, even after the counterLabel.text = String(number)
app was closed. }
if let lastDate = defaultValues.object(forKey: "lastDate") as? Date {
let calendar = Calendar.current
Do It Yourself: Create a new project. Add a Stepper and a label to let components = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second],
from: lastDate, to: Date())
the scene. Connect the Stepper to the view controller with an Outlet print("You haven't use this app in \(components.year!) years, \(components.month!)
called counter and the label with an Outlet called counterLabel. Connect months, \(components.day!) days, \(components.hour!) hours, \(components.minute!) minutes, \
(components.second!) seconds")
the Stepper with an Action called incrementValue(). Complete the }
ViewController class with the code in Listing 15-1 and run the defaultValues.set(Date(), forKey: "lastDate")
}
application. Press the buttons of the Stepper to change the value on @IBAction func incrementValue(_ sender: UIStepper) {
the label. Wait a few second for the system to register the change. let current = counter.value
defaultValues.set(current, forKey: "counter")
Stop the execution of the app from the Stop button in Xcode. Run counterLabel.text = String(current)
}
the application again. The value on the label should be the last one }
stored by the app. 

As we already mentioned, we can also store values generated by the app. Do It Yourself: Update the ViewController class with the code in
For example, we may add a value to User Settings to keep track of the time Listing 15-2 and run the application. Wait a few seconds and stop the
the user has spent without using the app. The following example updates
the last view controller to store this value. The code reads the value when

application from Xcode. Wait a few more seconds and run it again. 
You should see the time between runs printed on the console. import UIKit

class ViewController: UIViewController {


The interface introduced in Figure 15-1 was designed for didactic purposes, @IBOutlet weak var textEditor: UITextView!
but scenes presenting the app's settings are usually set apart from the override func viewWillAppear(_ animated: Bool) {
main interface. In Figure 15-2, below, we introduce a new interface with a super.viewWillAppear(animated)
dedicated scene to set the app's settings. The initial scene was embedded
let defaultValues = UserDefaults.standard
in a Navigation Controller. This scene contains a Text View and a bar button if let color = defaultValues.object(forKey: "color") as? Int {
called Settings to navigate to the settings scene. On the other hand, the let colorList = [UIColor.black, UIColor.gray, UIColor.lightGray]
textEditor.textColor = colorList[color]
settings scene was created with a Table View Controller and a static table }
with two sections and three cells to present the controls users can if let editable = defaultValues.object(forKey: "editable") as? Bool {
manipulate to set their preferences. The cell in the first section contains a textEditor.isEditable = editable
}
Segmented Control with three buttons to set the color of the text: Dark, if let correction = defaultValues.object(forKey: "correction") as? Bool {
Medium, and Light, and the cells in the second section contain Switches to if correction {
textEditor.spellCheckingType = .yes
set whether the Text View is going to be editable and have auto-correction } else {
activated or not. textEditor.spellCheckingType = .no
}
}
Figure 15-2: Interface with an additional scene for the app's settings }
}

For this example, we use three values called "color", "editable", and
"correction" to store the current settings. If the values were already set,
which means that the user changed them from the settings screen at some
point in the past, we configure the Text View with those values, otherwise
the application uses the values assigned in the Storyboard. For example,
the color for the text in the Text View is set as black by default in the

Storyboard, but if there is a value stored with the key "color" we use it to
get the current color from an array (black, gray, or light gray).
The view controller for the initial scene reads the values and updates the
In the view controller for the settings scene, we must read the values as
Text View.
well to update the elements, but also get the user’s input and store the
new values for future reference. We call this view controller
Listing 15-3: Configuring the Text View according to the values set by the
SettingsViewController.
user
we use in the viewDidLoad() method to update their states according to the
Listing 15-4: Saving the settings values selected by the user, and also to Actions that update the settings
 every time these values are changed (the Value Changed event is fired). In
import UIKit consequence, every time the user opens the settings scene, the states of
the elements are modified according to the values stored in User Settings,
class SettingsViewController: UITableViewController {
@IBOutlet weak var controlColors: UISegmentedControl! and as soon as the user moves a Switch or taps a button in the Segmented
@IBOutlet weak var controlEditable: UISwitch! Control, the new value is stored in the system.
@IBOutlet weak var controlCorrection: UISwitch!
var defaultValues: UserDefaults!
Do It Yourself: Create a new project. Embed the scene in a
override func viewDidLoad() {
super.viewDidLoad() Navigation Controller. Add a bar button called Settings and a Text
View. Add a Table View Controller to the Storyboard. Connect the
defaultValues = UserDefaults.standard
if let color = defaultValues.object(forKey: "color") as? Int { Settings button to the new scene with a Show Segue. Select the
controlColors.selectedSegmentIndex = color table, go to the Attributes Inspector panel, and change the Content
}
if let editable = defaultValues.object(forKey: "editable") as? Bool { option to Static Cells, and the value of Sections to 2. Select the
controlEditable.isOn = editable header of each section and erase their titles. Leave only one cell for
}
if let correction = defaultValues.object(forKey: "correction") as? Bool { the first section and two for the second section. Add labels, a
controlCorrection.isOn = correction Segmented Control with the buttons Dark, Medium, and Light, and
}
} switches to the interface (Figure 15-2, right). Create a subclass of the
@IBAction func saveColor(_ sender: UISegmentedControl) {
UITableViewController class called SettingsViewController and assign it to the
let current = controlColors.selectedSegmentIndex
defaultValues.set(current, forKey: "color") second scene. Complete the ViewController class and the
}
SettingsViewController class with the codes in Listings 15-3 and 15-4.
@IBAction func saveEditable(_ sender: UISwitch) {
let current = controlEditable.isOn Connect the elements to their Outlets and Actions. Run the
defaultValues.set(current, forKey: "editable")
} application and set new values. Wait a few seconds for the system to
@IBAction func saveCorrection(_ sender: UISwitch) { store the values in User Defaults. Stop the application and run it
let current = controlCorrection.isOn
defaultValues.set(current, forKey: "correction") again. The Text View should be always configured according to the
} values stored in the system.
}

So far, we have used a generic method to retrieve the values, but the
The settings scene contains a Segmented Control with the buttons Dark, methods for specific types provided by the UserDefault class can simplify our
Medium, and Light, and two Switches to set the edition and autocorrection code. The following example updates the ViewController class of Listing 15-3
features of the Text View. These elements were connected to Outlets that to read the values with the methods according to their type.

our app, it is good practice to set them when the app is launched, as in the
Listing 15-5: Reading values with specific methods following example.

import UIKit Listing 15-6: Setting values by default

class ViewController: UIViewController {
@IBOutlet weak var textEditor: UITextView! import UIKit

override func viewWillAppear(_ animated: Bool) { @main


super.viewWillAppear(animated) class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
let defaultValues = UserDefaults.standard [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let color = defaultValues.integer(forKey: "color") let list: [String : Any] = ["color": 0, "editable": true, "correction": false]
let colorList = [UIColor.black, UIColor.gray, UIColor.lightGray] let defaultValues = UserDefaults.standard
textEditor.textColor = colorList[color] defaultValues.register(defaults: list)
textEditor.isEditable = defaultValues.bool(forKey: "editable") return true
}
let correction = defaultValues.bool(forKey: "correction") func application(_ application: UIApplication, configurationForConnecting
if correction { connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
textEditor.spellCheckingType = .yes UISceneConfiguration {
} else { return UISceneConfiguration(name: "Default Configuration", sessionRole:
textEditor.spellCheckingType = .no connectingSceneSession.role)
} }
} }
} 

Do It Yourself: Replace the code in your ViewController class with the
The code is simplified because we no longer need to unwrap the values, code in Listing 15-5. Update the AppDelegate class with the code in
but there is a problem. Some of these methods return values by default.
Listing 15-6. Open the simulator, press the Home button, and delete
For example, the integer() method will return the value 0 if no value was
the application to clean the User Default system (the simulator
stored before for the "color" key. In the case of the color, we want the
works exactly like a real device, you have to keep pressing the mouse
value by default to be 0, so the text is displayed in black the first time the
user runs the application, but the value by default for Booleans is false, button on the icon of the app you want to delete and click the
which means that the features we set for the Text View will be deactivated Remove App option). Run the application again. The settings should
by default. If we want these features activated, we must provide our own be set according to the values you specified in the list dictionary.
default values. The UserDefaults class includes the register() method to set
these values. The method receives a dictionary of type [String: Any] with the
keys and values we want to set by default and stores them in the User
Defaults system. Because the values may be modified from anywhere in
15.2 Files attributes argument is a dictionary with values that determine the
directory’s attributes (e.g., ownership). The value nil sets the

attributes by default, which are usually enough for iOS applications.
iOS, like any other operative system, allows users to create and access files, createFile(atPath: String, contents: Data?, attributes:
but the iOS file system is like no other. The system restricts applications to Dictionary?)—This method creates a file. The atPath argument
their own space (called Sandbox) and a handful of directories to ensure specifies the location of the file, including its name and extension. The
that they do not interfere with each other. This means that we can only contents argument represents the content of the file. And the
access the files and directories that belong to our app. attributes argument is a dictionary with values that determine the
Foundation defines a class called FileManager to manage files and
file’s attributes (e.g., ownership). The value nil sets the attributes by
directories. One object of this class is assigned to the app and from it we
default.
can create, delete, copy, and move files and directories in our app's storage
space. The class offers the following type property to get a reference to this contents(atPath: String)—This method returns the content of the
object. file at the path specified by the atPath argument. The value returned
is an optional of type Data.
default—This type property returns a reference to the app's contentsOfDirectory(atPath: String)—This method returns an
FileManager object. array with the paths of the files and directories inside the directory
indicated by the atPath argument.
The FileManager class offers multiple properties and methods to manage
copyItem(atPath: String, toPath: String)—This method copies
files and directories. The following are the most frequently used.
the file or directory at the path specified by the at argument to the
path specified by the to argument.
urls(for: SearchPathDirectory, in: SearchPathDomainMask)—
This method returns an array with the location of a directory. The for moveItem(atPath: String, toPath: String)—This method moves
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, such as Documents, and the in argument is an path specified by the to argument.
enumeration with values that determine the domain in which the files removeItem(atPath: String)—This method removes the file or
are located. directory at the path indicated by the atPath argument.
createDirectory(at: URL, withIntermediateDirectories: Bool, fileExists(atPath: String)—This method returns a Boolean value
attributes: Dictionary?)—This method creates a new directory. The that determines whether the file or the directory at the path specified
at argument specifies the location, including the name and the by the atPath argument exists or not.
extension. The withIntermediateDirectories argument indicates attributesOfItem(atPath: String)—This method returns a
whether intermediate directories will also be created. And the dictionary with the attributes of the file or directory at the location

indicated by the atPath argument. The FileManager class provides URLs and Paths
constants to define the attributes, including creationDate, modificationDate,

size, and type, among others.

As in any other operative system, files in iOS are organized in directories.


There is a basic directory called root that can contain files and other
directories, which in turn can contain other files and directories, creating a
tree-like structure. To reproduce this route in code, iOS adopts a common
syntax that separates each part of the route with a forward slash (/),
starting with a single slash to indicate the root directory, as in
/Pictures/Travels/Hawaii.png. This is called path, and it is used to access
any file on the system.
Paths are a simple way to access files but are not enough to identify the
location of a file in the storage system. This is because in practice files are
usually not stored in a single storage space, but in several units or even in
remote servers. Following a long path to find a file also takes time and
consumes resources. For these reasons, locations are always identified by
URLs (Uniform Resource Locator). URLs are just strings, like paths, but
provide more information to the system and follow a convention that
systems can use to manage and access local or remote files. Foundation
defines a structure to work with URLs called URL. The following are some
of the initializers, properties and methods provided by this structure to
work with files.

URL(fileURLWithPath: String)—This initializer returns a URL object


referencing a local file or directory. The fileURLWithPath argument is
a string with the file's path.
URL(fileURLWithPath: String, relativeTo: URL)—This initializer
returns a URL object referencing a local file or directory. The
fileURLWithPath argument is a string with the file's path, and the
relativeTo argument is a URL object referencing the base URL.
path—This property returns the path of a URL. It is a conditional of Files and Directories
type String.

pathComponents—This property returns an array of String values
that represent the components of a path extracted from a URL (the When the application is installed on the device, iOS creates a group of
strings between forward slashes). standard directories that we can use. The most useful are the Documents
lastPathComponent—This property returns a string with the last directory, where we can store the user’s files, and the Application Support
directory, for files that our app needs to create during runtime. The
component of a path extracted from a URL. It is usually used to get
location of these directories is not guaranteed, so we must always ask iOS
the file’s name and extension.
for the current URL that points to the directory or file we want to access.
pathExtension—This property returns a string with the extension of To get the location of common directories like Documents, the FileManager
the path extracted from a URL. It is usually used to get the file’s class includes the urls() method. The method requires two arguments. The
extension. first argument is an enumeration with values that represent different
appendingPathComponent(String)—This method returns a new directories. There are several values available for the iOS and OSX
URL structure with the URL of the original object plus the component operative systems, including documentDirectory to reference the Documents
directory and applicationSupportDirectory to reference the Application Support
specified by the argument.
directory. The second argument is another enumeration with values that
indicate the domain where the directory is located. The system organizes
IMPORTANT: The FileManager class includes methods to work with
directories and files in separate domains depending on their intended
both paths and URLs. For example, there are two versions of the
usage. The domain where our app’s files are stored is the User Domain,
createDirectory() method, one to create the directory from a path and
identified with the constant userDomainMask. In consequence, to get the URL
another from a URL. The methods that work with paths take care of of any of the directories generated for our app and start creating files to
converting the path into a URL and are usually easier to implement, store the user’s data, we must get a reference to the FileManager object and
but you can use any method you need. For a complete list, visit our then call the urls() method with the values that represent the location we
website and follow the links for this chapter. want to use. The following example shows how to get the Documents
directory’s URL.

Listing 15-7: Getting the Documents directory’s URL



import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
let manager = FileManager.default

let documents = manager.urls(for: .documentDirectory, in: .userDomainMask) let newFileURL = docURL.appendingPathComponent("mytext.txt")


let docURL = documents.first! let path = newFileURL.path
print(docURL) manager.createFile(atPath: path, contents: nil, attributes: nil)
} }
} }
 

The urls() method returns an array of optional URL structures with all the The path must represent the route to the file, including its name and
possible locations of the directory. In the case of the Documents directory, extension. The URL class includes the appendingPathComponent() method to
the right value is the first item of the array, so in Listing 15-7 we get this extend a URL. The method adds a string to the end of the URL and takes
value from the first property, unwrap it, assign it to the docURL constant, care of including the necessary forward slashes to ensure its validity. In
and print it on the console. Listing 15-8, we use it to get the URL of the new file. After this, we obtain
its path from the path property, and finally call the createFile() method to
Do It Yourself: Create a new project. Update the ViewController class create the file (if the file already exists, the method does nothing). At the
with the code in Listing 15-7. Run the application. You should see the end, the Documents directory of this application contains an empty file
URL of the Documents directory printed on the console. called mytext.txt.
In the same way that we create a file, we can create a directory. The
Now that we have the URL, we can start adding other directories and files method to create a directory is called createDirectory(). Again, the method
inside the Documents directory. The FileManager class includes the createFile() takes three values: the path of the new directory (including its name), a
method to create new files. The method requires three values: the path Boolean value that indicates if we want to create all the directories
where the file is going to be created (including the file’s name and included in the path (in case the path includes directories we have not
extension), a value with the file's content, and the file’s attributes. The created yet), and the attributes. This method throws an error if it cannot
content and the attributes are optional. If we declare these values as nil, complete the task, so we must handle it with try.
the file is created empty and with attributes by default, as in the following
example. Listing 15-9: Creating a directory inside Documents

Listing 15-8: Creating a file inside Documents import UIKit

 class ViewController: UIViewController {


import UIKit override func viewDidLoad() {
super.viewDidLoad()
class ViewController: UIViewController { let manager = FileManager.default
override func viewDidLoad() { let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
super.viewDidLoad() let docURL = documents.first!
let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask) let newDirectoryURL = docURL.appendingPathComponent("myfiles")
let docURL = documents.first! let path = newDirectoryURL.path
do {
try manager.createDirectory(atPath: path, withIntermediateDirectories: false, attributes:
nil)
} catch { In this example, we get the Documents directory’s URL and then call the
print("The directory already exists") listItems() method to list the files and directories inside it. The listItems()
}
method gets its own reference of the FileManager object and then calls the
}
} contentsOfDirectory() method to get the list of items. Notice that this method
 throws errors, so we use try? to handle them. If the value returned is not nil
and not empty, we print every item on the console (in our case, this would
The FileManager class also offers methods to list the content of a directory. be the myfiles directory and the mytext.txt file created in previous
We can use the contentsOfDirectory() method to get an array of strings with examples).
the names of the files and directories in a specific path. In the following In the example of Listing 15-8, we have created a file inside the Documents
example, we create another method to list files and directories. The directory, but we can also create files inside our custom directories. For
method receives the URL we want to search for and then creates a for in example, we can add a new file to the myfiles directory created in Listing
loop to print the items found. 15-9.

Listing 15-10: Listing the content of a directory Listing 15-11: Creating files in a custom directory


import UIKit
import UIKit
class ViewController: UIViewController {
class ViewController: UIViewController {
override func viewDidLoad() {
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
let manager = FileManager.default
let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = documents.first!
let docURL = documents.first!
listItems(directory: docURL)
}
let newFileURL = docURL.appendingPathComponent("myfiles/anotherfile.txt")
func listItems(directory: URL) {
let path = newFileURL.path
let manager = FileManager.default
let created = manager.createFile(atPath: path, contents: nil, attributes: nil)
if let list = try? manager.contentsOfDirectory(atPath: directory.path) {
if !created {
if list.isEmpty {
print("We couldn't create the file")
print("The directory is empty")
}
} else {
}
for item in list {
}
print(item)

}
}
} The code in Listing 15-11 adds the necessary components to the
}
}
Documents directory’s URL and then creates the file as done before. In this
 example, we add the components all at once, separating each component

with a forward slash, but we could have called the appendingPathComponent() class ViewController: UIViewController {
override func viewDidLoad() {
method for every component and add them one by one. super.viewDidLoad()
We can also move a file or directory from one location to another. All we let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
have to do is to provide the paths for the origin and destination and then let docURL = documents.first!
call the moveItem() method. For example, we can move the mytext.txt file to
the myfiles directory created before. let originURL = docURL.appendingPathComponent("myfiles/anotherfile.txt")
let destinationURL = docURL.appendingPathComponent("anotherfile.txt")
let originPath = originURL.path
Listing 15-12: Moving files let destinationPath = destinationURL.path
do {

try manager.copyItem(atPath: originPath, toPath: destinationPath)
import UIKit } catch {
print("File was not copied")
class ViewController: UIViewController { }
override func viewDidLoad() { }
super.viewDidLoad() }
let manager = FileManager.default 
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = documents.first!
Removing files or directories is also an easy task. All we need to do is to get
let originURL = docURL.appendingPathComponent("mytext.txt") the path to the item and then call the removeItem() method.
let destinationURL = docURL.appendingPathComponent("myfiles/mytext.txt")
let originPath = originURL.path
let destinationPath = destinationURL.path Listing 15-14: Removing files
do { 
try manager.moveItem(atPath: originPath, toPath: destinationPath)
} catch { import UIKit
print("File was not moved")
} class ViewController: UIViewController {
} override func viewDidLoad() {
} super.viewDidLoad()
 let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = documents.first!
We can also copy files from one directory to another. The following
example copies the anotherfile.txt file created inside the myfiles directory let fileURL = docURL.appendingPathComponent("anotherfile.txt")
let path = fileURL.path
into the Documents directory. do {
try manager.removeItem(atPath: path)
} catch {
Listing 15-13: Copying files print("File was not removed")
 }
}
import UIKit
}

Files Attributes
Do It Yourself: Update the ViewController class with the codes in

Listings 15-11, 15-12, 15-13, and 15-14, and run the application
every time. Incorporate the listItems() method of Listing 15-10 to the Some applications need to know more than the name of the file. The
class so you can see the files being created and moved. FileManager class offers the attributesOfItem() method to get the file attributes,
such as the date of creation or the 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 useful are creationDate (the date the
file was created), modificationDate (last time it was modified), size, and type.
The following code gets the attributes of the mytext.txt file created in
previous examples.

Listing 15-15: Reading the file’s attributes



import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = documents.first!

let fileURL = docURL.appendingPathComponent("myfiles/mytext.txt")


let filePath = fileURL.path
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 {
print("Name: \(fileURL.lastPathComponent)")
print("Size: \(size)")
print("Created: \(date)")
}
}
}
}

The attributes are returned as Any values, so we cast them to their Files Content
corresponding data types. Most of the values are of data types we already

know, such as Int or Date, but the type key returns a property of a structure
called FileAttributeType that represent the resource's type. There are
Storage systems, like hard drives and solid-state drives, store information
properties available to represent different types of resources. The most
the only way a computer knows, as a series of ones and zeros. Therefore,
frequently used are typeRegular to represent files and typeDirectory to
the information that we want to store in files must be converted into a
represent directories. If we want to know if the resource is a file, we can
stream of bytes which can then be converted back to the original data.
compare the value returned by the type key with the typeDirectory property,
Foundation offers the Data structure for this purpose.
as we did in the example of Listing 15-15. If they are different, it means
Although we can work directly with a Data structure, most frameworks
that the resource is a file, and we can process its attributes.
include tools to convert our data into Data structures that we can process or
store on files. For example, the UIImage class includes the pngData() and
jpegData() methods to convert images into Data structures (see Chapter 5).
Another common data type that provides tools to convert data is the String
structure. This structure includes a method that turns a string into data an
also an initializer that can get back the string from a Data structure.

String(data: Data, encoding: Encoding)—This initializer creates a


String value with the text in the Data structure specified by the data
argument. The encoding argument is a value provided by properties
of a structure called Encoding that determine the type of encoding used
to generate the string. The most frequently used are unicode, utf8, and
ascii.

data(using: Encoding, allowLossyConversion: Bool)—This


method returns a Data structure containing the string in the original
String value. The using argument is a value provided by properties of
the Encoding structure that determine the type of encoding used to
generate the string. The most frequently used are unicode, utf8, and
ascii. The allowLossyConversion argument determines the precision of
the conversion.
The structures and classes capable of processing Data structures and files
also provide specific methods to write and read a file. For example, the
String structure includes the following method to turn a string into data and
store it in a file, all at once.

write(to: URL, atomically: Bool, encoding: Encoding)—This


method converts a string into a Data structure and stores it in the file
located at the URL specified by the to argument. The atomically
argument determines if we want the data to be stored in an auxiliary
file first to ensure that the original file is not corrupted (the value true
is recommended). The encoding argument is a value provided by
properties of the Encoding structure that determine the type of
encoding used to generate the string. The most frequently used are
unicode, utf8, and ascii.

With these tools, we can store information generated by the user in a file

and keep it updated. For example, we can get input from the user through
a Text View and store the text in a file.
The view controller for the scene in Figure 15-3 must perform two tasks:
store the text from the Text View in a file every time the user introduces a
Figure 15-3: Interface to generate file content
change, and read the content back from the file every time the user loads
the application and show it on the screen.

Listing 15-16: Managing the file’s content



import UIKit

class ViewController: UIViewController, UITextViewDelegate {


@IBOutlet weak var diaryText: UITextView!
var fileURL: URL!

override func viewDidLoad() {


super.viewDidLoad()

diaryText.delegate = self
let manager = FileManager.default

let documents = manager.urls(for: .documentDirectory, in: .userDomainMask) View to the ViewController class with an Outlet called diaryText.
let docURL = documents.first!
fileURL = docURL.appendingPathComponent("userdata.txt") Complete the ViewController class with the code in Listing 15-16. Run
let filePath = fileURL.path the application and insert a text in the Text View. Stop the
if manager.fileExists(atPath: filePath) {
if let content = manager.contents(atPath: filePath) { application from Xcode and run it again. You should see the text on
diaryText.text = String(data: content, encoding: .utf8)
the screen.
}
} else {
manager.createFile(atPath: filePath, contents: nil, attributes: nil)
}
}
func textViewDidChange(_ textView: UITextView) {
let text = diaryText.text!
do {
try text.write(to: fileURL, atomically: true, encoding: .utf8)
} catch {
print("Error")
}
}
}

The first thing we do in this ViewController class is to call the fileExists() method
to check if the file where we are going to store the data already exists. If
the file exists, we read its content with the contents() method, convert the
data into a string with the String initializer, and assign the string to the Text
View. This way, the last text stored in the file is always displayed on the
screen as soon as the app is launched. If the file does not exist, however,
we create it with the createFile() method and no content (nil). To keep the file
updated with the text inserted by the user in the Text View, we declare the
ViewController class as the delegate of the Text View and implement the
textViewDidChange() method. This method is called by the Text View every
time its content changes (see Chapter 5). Inside the method we read the
current text and call its write() method to store it in the file. Now, everything
the user writes in the Text View is preserved and always available.

Do It Yourself: Create a new project. Add a label and a Text View to


the initial scene to get the interface in Figure 15-3. Connect the Text
Bundle The Bundle object is frequently used in professional applications to access
files that are required for some services, like databases, for example, but

we can also take advantage of this object to load files with initial data to
populate the model when the app is launched for the first time or to
A bundle is a container for all the app's files and directories. One bundle is
restore the initial state.
created per app, so by accessing the bundle we get access to all the files
The following example loads a text file called quote.txt from the bundle as
included in the application. To create and manage bundles, Foundation
soon as the application is launched. The file must be added to the project
defines the Bundle class. The class offers properties and methods to work
during development by dragging it from Finder to the Navigator Area, as
with bundles, including the following to access the app's bundle.
we did for images before (see Figure 5-97).

main—This type property returns a reference to the app’s bundle. Listing 15-17: Loading a file added to the project
bundleURL—This property returns a URL structure with the bundle’s 
URL. import UIKit

bundlePath—This property returns a string with the bundle’s path. @main


class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
Because we are not able to determine the location of our app’s files and [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
resources during development, every time we want to access these files let bundle = Bundle.main
let filePath = bundle.path(forResource: "quote", ofType: "txt")
from code, we need to get their URLs from the Bundle object. The Bundle
class provides the following methods for this purpose. let manager = FileManager.default
if let data = manager.contents(atPath: filePath!) {
let message = String(data: data, encoding: .utf8)
url(forResource: String?, withExtension: String?)—This method print(message!)
returns a URL structure with the URL of a file or directory inside the }
return true
bundle. The first argument specifies the name of the file or directory }
func application(_ application: UIApplication, configurationForConnecting
we are looking for, and the withExtension argument specifies its connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
extension (set as nil for resources that do not have an extension). UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole:
path(forResource: String?, ofType: String?)—This method connectingSceneSession.role)
returns the path of a file or directory inside the bundle. The first }
}
argument specifies the name of the file or directory we are looking 
for, and the ofType argument specifies its extension (set as nil for
resources that do not have an extension). The code in Listing 15-17 gets a reference to the app’s bundle and then
finds the path for the quote.txt file with the path() method. After we get the
path, we can read the file as we did before. In this example, we print the

string on the console, but this information is usually stored in the model or 15.3 Archiving
assigned to the objects that requires it.

Do It Yourself: Create a new project and update its AppDelegate class
The methods we have just studied to store data in files are enough for
with the code in Listing 15-17. Create a text file with a text editor,
simple models but present some limitations. We can only work with single
write some text in it, save it with the name quote.txt, and add it to values and with classes that already provide a way to turn their content
your project. Run the application. You should see the text inside the into data. Professional applications rely on more elaborated models that
file printed on the console. include collection of values and custom objects. To give us more flexibility,
Foundation offers the NSCoder class. This class can encode and decode
values to Data structures for storage purposes in a process called Archiving.
An NSCoder object not only encodes an object but also the objects it is
connected to, preserving the connections and the hierarchy. For example,
we may have two objects with properties that reference the other object.
Object 1 references Object 2 and Object 2 references Object 1. With
archiving, both objects are encoded, stored, and then decoded and
connected again when we need them.
Encoding and Decoding Implementing these methods, we can generate Data structures for
processing, or we can just store and retrieve the data from a file. In the

following example, we encode and decode a string to a Data structure and
use FileManager methods to create and read the file.
The NSCoder class provides all the methods necessary to encode and
decode the values of an object, but all the work is done by instances of two
Listing 15-18: Encoding and decoding data
NSCoder subclasses called NSKeyedArchiver and NSKeyedUnarchiver. The

NSKeyedArchiver class calls the encode() method on the objects to encode their
import UIKit
values and stores the data in a Data structure or a file. The NSKeyedUnarchiver
class, on the other hand, initializes the objects with the protocol’s initializer class ViewController: UIViewController {
override func viewDidLoad() {
and returns the original values. super.viewDidLoad()
The NSKeyedArchiver class offers the following type method to encode an
Object Graph and store it in a Data structure. let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = documents.first!
archivedData(withRootObject: Any, requiringSecureCoding:
let fileURL = docURL.appendingPathComponent("quotes.dat")
Bool)—This type method encodes the Object Graph of the object let filePath = fileURL.path
specified by the withRootObject argument and stores it in a Data if manager.fileExists(atPath: filePath) {
if let content = manager.contents(atPath: filePath) {
structure. The requiringSecureCoding argument is a Boolean value if let result = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSString.self, from:
content) as String? {
that determines whether the data will be secured or not.
print(result)
}
The NSKeyedUnarchiver class offers the following type method to decode an }
} else {
Object Graph from a Data structure. let quote = "Fiction is the truth inside the lie"
if let fileData = try? NSKeyedArchiver.archivedData(withRootObject: quote,
requiringSecureCoding: false) {
unarchivedObject(ofClass: Class, from: Data)—This type manager.createFile(atPath: filePath, contents: fileData, attributes: nil)
method decodes the data specified by the from argument and returns }
}
the original Object Graph. The ofClass argument determines the data }
type of the decoded object. }

unarchivedObject(ofClasses: [AnyClass], from: Data)—This
type method decodes the data specified by the from argument and Listing 15-18 presents a view controller with a simple example. A single
returns the original Object Graph. The ofClasses argument string is encoded and stored in a file called quotes.dat. As we did in
determines the data types of the decoded object. previous examples, we first check if the file exists with the fileExists()
method and then proceed accordingly. If the file does not exist, we convert

the string in the quote constant to a Data value with the archivedData() method Another requirement for custom structures is that they implement the
and create a file with it, but if the file already exists, we read its content initializers and methods defined in the NSSecureCoding protocol for the
with the contents() method and decode the data with the unarchivedObject() system to be able to encode and decode the data. Fortunately, the Swift
method to get back the string. Standard Library defines a protocol called Codable that turns a structure into
The unarchivedObject() method can only work with data types that conform to an encodable and decodable data type. All we need to do, is to make our
a protocol called NSSecureCoding. That is the reason why we had to specify structure conform to the protocol and the compiler takes care of adding all
the NSString class as the value of the ofClass argument and convert it at the the methods required to encode and decode its values. The following
end to a String structure with the as operator (the NSString class conforms to example creates a structure to store information about books.
the NSSecureCoding protocol but the String structure does not).
Listing 15-19: Encoding and decoding a custom data type
Do It Yourself: Create a new project. Update the ViewController class 
with the code in Listing 15-18. Run the application. The first time, import Foundation
the file is created, and no message is printed on the console. Stop struct Book: Codable {
and run the application again. Now you should see the quote on the var title: String
var author: String
console. var edition: Int
}

The methods of the NSKeyedArchiver and NSKeyedUnarchiver classes work with
Property List values (NSNumber, NSString, NSDate, NSArray, NSDictionary, NSData,
Once our custom structure is defined, we can create instances of it and
and the equivalents in Swift). In the previous example, we had to use an
archive them. The next example follows the same procedure as before.
NSString value, but if we want to archive our own data types, we must
When the main view is loaded, we check if the file already exists and read
convert them to Property List values. Foundation offers two classes for this
it or create a new one. To archive and unarchive the data, we use the same
purpose, PropertyListEncoder and PropertyListDecoder, which include the
methods, but this time we encode or decode the values into a Property
following methods to encode and decode values.
List.

encode(Value)—This method of the PropertyListEncoder class encodes Listing 15-20: Encoding and decoding arrays of custom structures
a value into a Property List value. 
decode(Type, from: Data)—This method of the PropertyListDecoder import UIKit
class decodes a Property List value into a value of the data type class ViewController: UIViewController {
specified by the first argument. The from argument is a Data structure override func viewDidLoad() {
super.viewDidLoad()
with the data to be decoded.
let manager = FileManager.default
let documents = manager.urls(for: .documentDirectory, in: .userDomainMask)
let docURL = documents.first! Do It Yourself: Create a new project. Create a new Swift file called
let fileURL = docURL.appendingPathComponent("userdata.dat") Book.swift for the structure in Listing 15-19. Update the ViewController
let filePath = fileURL.path class with the code in Listing 15-20. As happened with the previous
if manager.fileExists(atPath: filePath) {
if let content = manager.contents(atPath: filePath) { example, the first time you run the application, the file is created
if let result = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSData.self, from:
content) as Data? {
and nothing is printed on the console, but if you run the application
let decoder = PropertyListDecoder() again, the view controller decodes the content of the file created
if let books = try? decoder.decode([Book].self, from: result) {
for book in books {
before and prints the values of every Book object on the console.
print("\(book.title) - \(book.author) - \(book.edition)")
}
}
}
}
} else {
let book1 = Book(title: "IT", author: "Stephen King", edition: 2)
let book2 = Book(title: "Pet Sematary", author: "Stephen King", edition: 1)
let book3 = Book(title: "The Shining", author: "Stephen King", edition: 1)
let list = [book1, book2, book3]

let encoder = PropertyListEncoder()


if let data = try? encoder.encode(list) {
if let fileData = try? NSKeyedArchiver.archivedData(withRootObject: data,
requiringSecureCoding: false) {
manager.createFile(atPath: filePath, contents: fileData, attributes: nil)
}
}
}
}
}

In this example, we store an array of three Book structures. First, we convert


the array into a Property List value with the encode() method of the
PropertyListEncoder class, then we archive the data, and finally we store it in a
file called userdata.dat. To get the information back from the file, we
decode the data with the decode() method of the PropertyListDecoder class and
print the values on the console.

15.4 Core Data Data Model


 

With archiving, we can store not only objects but also their connections. The structure of the Core Data’s Object Graph is defined with a data model.
This organization is called Object Graph. Archiving is a good tool to store an This has nothing to do with the data model of the MVC pattern
Object Graph on file but presents some limitations. The Object Graph implemented in previous chapters; a Core Data model is a definition of the
stored this way is difficult to expand or modified. The entire graph must be type of objects the graph is going to contain (called Entities) and their
stored in the file again after the smallest change, and it is not easy to connections (called Relationships).
control the connections between objects to determine exactly which A model can be created from code, but Xcode offers a practical editor to
objects will be stored. The solution is called Core Data. Core Data is an define the structure of the graph. The model is stored in a file and then the
Object Graph manager that defines and manages its own objects and file is compiled and included in the Core Data system created for our app.
connections and stores them in a database. We can determine the Xcode offers a template to create this file.
composition of the objects and their relationships. The system takes care
of encoding and decoding the objects, preserving consistency and Figure 15-4: Option to create a Core Data model in the iOS panel
maximizing efficiency.

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.

Figure 15-5: Model editor


(properties). To add an attribute, we select the entity and press the +
button under the Attributes area (Figure 15-6, number 3) or press the Add
Attribute button at the bottom of the editor (Figure 15-5, 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 editing the Name field in the Data Model Inspector panel.
For our example, we called the entity Books and the first attribute title
(Figure 15-7, number 1).

 Figure 15-7: New Attribute

The model contains three main components: Entities, Attributes, and


Relationships. Entities are the descriptions of the objects, Attributes are
the descriptions of the objects’ properties, and Relationships are the
connections allowed between the objects. The first step is to add Entities
to the model. Entities are created from the Add Entity button at the 
bottom of the editor (Figure 15-5, number 1). When we press this button,
Xcode creates an entity with the generic name "Entity". IMPORTANT: The name of entities must start with an uppercase
letter and the names of attributes and relationships with a lowercase
Figure 15-6: New Entities letter.

Every attribute must be associated with a data type for the objects to know
what kind of values they can manage (Figure 15-7, number 2). Clicking on
the attribute’s type, we can open a menu to select the right data type. The
most frequently used are Integer 16, Integer 32, Integer 64, Double, Float,

String, Boolean, Date, and Binary Data. The Integer 16, 32, or 64 options
are for Int16, Int32, and Int64 values, Double and Float are for Double and Float
We can change the name of the newly created entity by double-clicking
values, String is for String values, Boolean is for Bool values, Date is for Date
the name (Figure 15-6, number 1) or by editing the Name field in the Data
values, and Binary Data is for Data values.
Model Inspector panel (Figure 15-6, number 2).
An entity may contain as many attributes as our objects need. For example,
An entity defines the composition of the objects that are going to be part
we may add a few more attributes to complement the information
of the Object Graph, so the next step is to declare the type of values those
required for books.
objects are going to store. For this purpose, entities include Attributes

Figure 15-8: Multiple Attributes



We could have also included another attribute for the author’s name in the
Books entity, but here is when we need to think about the structure of the
In the example of Figure 15-8, we added an attribute called year to store
Object Graph and how the information will be stored. If we include a String
the year in which the book was published, and two attributes of type
type attribute for the author's name inside the Books entity, every time the
Binary Data to store images (the book's cover and thumbnail). The data
user inserts a new book it will have to type the name of the author. This is
types used by these attributes are analog to the Swift data types we have
error prone, time consuming, and when several books of the same author
being working so far. The title attribute takes a String value, the year
are available, it is impossible to make sure that all share the same exact
attribute stores a value of type Int16, and the images are going to be stored
name (one book could have the author’s middle name and others just the
as Data structures.
first one, for example). Without the certainty of having the exact same
Most values don't require much consideration, but images are made of big
name, we can never incorporate features in our app such as ordering the
chunks of data. Storing large amounts of data in a Persistent Store can
books by author or getting the list of books written by a particular author.
affect the system's performance and slow down essential processes like
Things get worse when, along with the name, we also decide to store other
searching for values or migrating the model. One alternative is to store the
information about the author, like his or her date of birth. A proper
images in separate files, but it can get cumbersome to coordinate
organization of this information demands separate objects and therefore
hundreds of files with the data in the database. Fortunately, Core Data can
we must create new entities to represent them. Additional entities are
perform the process for us. All we need to do is to store the image as
added to the model in the same way as we did with the first one. Figure
Binary Data and select the option Allows External Storage, available in the
15-10, next, shows our model with a new entity called Authors containing
Data Model inspector panel inside the Utilities Area, as shown in Figure 15-
an attribute called name.
9, below. After the option is selected, the images assigned to that attribute
are going to be stored in separate files managed by the system.
Figure 15-10: Multiple Entities
Figure 15-9: Option to store images outside the Persistent Store


Entities are blueprints that we use to define the characteristics of the
objects we want to store. For instance, when we want to store a new book
in our example, we create a new object based on the Books entity. That
object will have four properties corresponding to the values of its title,
year, cover, and thumbnail. The same happens when we want to store
information of an author. We create a new object based on the Authors
entity and assign the name of the author to its name property. At the end, 
we will have two objects in the storage space, one for the book and
another for the author. But if we want to retrieve these objects later, we A relationship only needs two values: its name (the name of the property)
need a way to know what Books object is related to what Authors object. and the destination (the type of objects it is referencing), but it requires
To create this connection, the Core Data model includes Relationships. some parameters to be set. We must tell the model if the relationship is
Relationships are like properties in one object containing references to going to be optional, define its type (To-One or To-Many), and determine
other objects. They could have a reference to only one object or a set of what should happen to the destination object if the source object is
objects. For example, in the Books entity, we can create a relationship that deleted (the Delete Rule). All these options are available in the Data Model
contains a reference to only one object of the Authors entity, because Inspector panel when the relationship is selected.
there could only be one author per book (for this example we are assuming
that our app is storing books written only by one author). On the contrary, Figure 15-12: Relationship settings
in the Authors entity, we need to establish a relationship that contains
references to multiple Books objects, because an author may have written
several books, not just one. Core Data calls these relationships according to
the number of objects they may reference. The names are To-One and To-
Many and they are created pressing the + button in the Relationships area
below the Attributes area. Figure 15-11, below, shows a relationship called
author we have created for the Books entity of our example.

Figure 15-11: Relationship for the Books entity

By default, the relationship is set to 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


is not deleted (if there is an Authors object assigned to the Books
object, the book is not deleted).
Nullify: The connections between objects are removed, but the
objects at the destination are not deleted (if a Books object is 
deleted, the Authors object associated with that book loses the
connection but it is not deleted). IMPORTANT: Relationships must always be bidirectional. If we set
Cascade: The objects at the destination are deleted when the a relationship from entity A to entity B, we must set the opposite
source is deleted (the Authors object is deleted if one of its books is relationship from entity B to A. Core Data offers another type of
deleted). relationship called Fetched Properties to connect entities in only one
No Action: The objects at the destination are not deleted or direction. You can add a Fetched Property from the area below the
modified (the connections are preserved, even when the source Relationships area.
object does not exist anymore).
The new relationship added in Figure 15-13 is in the Authors entity, so
To find the right rule for a relationship, we must think in terms of the every Authors object will have a property called books that we can use to
information we are manipulating. Is it right to delete the author if one of retrieve the Books objects connected to the author. Because one author
its books is deleted? In our case, the answer is simple. An author can have can have many books, the setting of this relationship is going to differ from
more than one book, so we cannot delete the author when a book is the previous one. In this case, we must set the Type of the relationship as
deleted because there could be other books that are connected to that To-Many (to many books) and modify the Delete Rule according to how we
same author. Therefore, the Nullify rule set by default is the right one for want our application to respond when an author is deleted. If we don't
this relationship. But this could change when we create the opposite want to keep books that do not have an author assigned, we should select
relationship, connecting the Authors entity to the Books entity. We need the Cascade option, so when an author is deleted all his or her books are
this second relationship to search for books that belong to an author. deleted too. But if we don't mind having books with no author around,
Figure 15-13 shows a relationship called books that we have created for the then the option should be kept as Nullify.
Authors entity.
IMPORTANT: The Delete Rules are a way to ensure that the objects
Figure 15-13: Relationship for the Authors entity 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 recommended 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 15-
14 shows the final setup for both relationships.

Figure 15-14: Inverse Relationships


Do It Yourself: Create a new project. Open the File menu at the top
of the screen, go to New and select the File option to create a new
file. Move to the Core Data section in the iOS panel and select the
Data Model option (Figure 15-4). Save the file with the name
books.xcdatamodeld. Click on this file to open the editor (Figure 15-
 5). Press the Add Entity button to create two new entities with the
names Authors and Books. Create the attributes for these entities as
Two relationships are simple to follow, but multiple relationships illustrated in Figures 15-8 and 15-10. Create the relationships for
connecting several entities together can turn the model into an every entity as shown in Figure 15-14. Set the books relationship to
indecipherable mess. To help us identify every component of the model, To-Many and keep the rest of the values by default. Click on the
Xcode offers an additional visualization style that displays the entities as Editor Style button to see a graphical representation of the model.
boxes and the relationships as arrows connecting the boxes. The option,
called Editor Style, is at the bottom of the Editor Area (Figure 15-5, number
3). Figure 15-15, below, shows what our model looks like when we switch
to this style (Notice that the To-Many relationship is represented by double
arrows).

Figure 15-15: Graphical representation of the model

Core Data Stack NSManagedObjectContext creates and manages the context that intermediates
between our app and the Persistent Store. Although we can instantiate

these objects and create the stack ourselves, the framework offers the
NSPersistentContainer class, which takes care of everything for us. The class
The creation of the model is just the first step in the definition of the Core
includes the following initializer and also properties to access each object
Data system. Once we have all the entities along with their attributes and
of the stack.
relationships set up, we must initialize Core Data. Core Data is created from
a group of objects that manage all the processes, from the organization of
NSPersistentContainer(name: String)—This initializer creates an
the Object Graph to the storage of the graph in a database. There is an
object that defines a Core Data stack. The name
NSPersistentContainer
object that manages the model, an object that stores the data on file, and
an object that intermediates between this Persistent Store and our own argument is a string representing the name of the container. This
code. The scheme is called stack. Figure 15-16 illustrates a common Core value must match the name of the Core Data model (the file's name,
Data stack. without the extension).
managedObjectModel—This property sets or returns an
Figure 15-16: Core Data stack NSManagedObjectModel object that represents the Core Data model.
persistentStoreCoordinator—This property sets or returns the
NSPersistentStoreCoordinator object that manages 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.


To create the Core Data stack from our app, we must initialize a new
NSPersistentContainer object and then load the Persistent Stores (one by
The code in our application interacts with the Context to manage the
default). Because the stores may take time to load, the class offers a
objects and access their values, the Context asks the Persistent Store to
specific method for this purpose.
read or add new objects to the graph, and the Persistent Store processes
the Object Graph and saves it in the database.
The Core Data framework offers classes to create objects that represent
loadPersistentStores(completionHandler: Closure)—This
every part of the stack. The NSManagedObjectModel class manages the model, method loads the Persistent Stores and executes a closure when the
the NSPersistentStore class manages a Persistent Store, the process is over. The closure receives two arguments, an
NSPersistentStoreCoordinator class is used to manage all the Persistent Stores NSPersistentStoreDescription object with the configuration of the stack, and

available (a Core Data stack can have multiple Persistent Stores), and the an optional NSError value to report errors.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
All the communication between our app and the data in the Persistent func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
Store is done through the context. The context is created by the container [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
from the NSManagedObjectContext class. This class includes properties and
}
methods to manage the context and the objects in the Persistent Store. func application(_ application: UIApplication, configurationForConnecting
The following are the most frequently used. connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole:
hasChanges—This property returns a Boolean value that indicates if connectingSceneSession.role)
}
the context has changes that have to be saved in the Persistent Store. lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "books")
save()—This method saves the changes in the Persistent Store. container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
reset()—This method resets the context to a basic state. All the fatalError("Unresolved error \(error), \(error.userInfo)")
objects and modifications our app introduced to the context are }
})
ignored. return container
delete(NSManagedObject)—This method deletes an object in the }()
}
Persistent Store. 

count(for: NSFetchRequest)—This method returns the number of


The AppDelegate class in Listing 15-21 defines a computed property called
objects found in the Persistent Store for the request. The for
persistentContainer to create the Persistent Store. Apple recommends
argument specifies the request.
declaring this property as lazy, so the Core Data stack is only initialized
when required. The property creates an instance of the NSPersistentContainer
When working with Core Data, the Core Data's Persistent Store becomes
class with the name of the Core Data model (in our example, we called it
our app's data model. Therefore, Apple recommends initializing the Core
"books"). This object creates the stack but does not load the Persistent
Data stack in the app’s delegate. In fact, Xcode includes an option to create
Stores; this is the job of the loadPersistentStores() method. After completion,
the Core Data stack that generates this code for us. All we need to do is to
this method executes a closure with two values: a reference to the
activate the Use Core Data option when the project is created (see Figure
Persistent Store just created, and an Error value to report errors. Errors are
5-10). The following example shows the code included by the Xcode
infrequent, but if an error occurs, we should warn the user. The code
template when this option is selected.
provided by Xcode just calls the fatalError() function to stop the execution of
the app.
Listing 15-21: Initializing the Core Data stack in the app’s delegate
The NSManagedObjectContext object stored in the viewContext property of the

Persistent Store is the one we use in our app's view controllers to add,
import UIKit
import CoreData fetch, and remove objects from the store. To read this property from the
view controllers we must access the app's delegate, as explained in

Chapter 5 (see Listing 5-10). The following code illustrates a possible Managed Objects
implementation. We read the property in the app's delegate and assign its

value to a local property that we can use later from other methods in the
view controller to access the context.
Core Data does not store our custom objects; it defines a class called
NSManagedObject for this purpose. Every time we want to store information
Listing 15-22: Accessing the context from a view controller
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
import UIKit
import CoreData 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
class ViewController: UIViewController {
var context: NSManagedObjectContext!
(title, year, cover, thumbnail, and author). The class includes the following
initializer and methods to create and manage the objects.
override func viewDidLoad() {
super.viewDidLoad()
let app = UIApplication.shared NSManagedObject(context: NSManagedObjectContext)—This
let appDelegate = app.delegate as! AppDelegate initializer creates an instance of the NSManagedObject class and adds it
context = appDelegate.persistentContainer.viewContext
} to the context specified by the argument.
}
 fetchRequest()—This type method generates a fetch request for an
entity. A fetch request is a request we use to fetch objects of a
Do It Yourself: Update the AppDelegate class with the code in Listing particular entity from the Persistent Store.
15-21. Replace the value of the name argument in the
NSPersistentContainer initializer by the name of your model's file To simplify our work, the system allows us to define subclasses of the
(books). At this moment, the app doesn't do anything other than NSManagedObject class that correspond to the entities of our model (Instead

creating the stack. of creating instances of the NSManagedObject class, we create instances of
the Books and Authors classes). Because this is common practice for any
developer, Xcode does it automatically for us. All we need to do is to
associate each Entity with a subclass from the Data Model Inspector panel.
The option is available in a section called Class.

Figure 15-17: 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
the Codegen option and work directly with NSManagedObject objects or
define your own subclasses.

To ask Xcode to create the subclasses for us, we must select the entities
one by one, click on the Class Definition value for the Codegen option
(number 2), and make sure that the name of the subclass is specified in the
Name field (number 1). Once the options are set, the classes are
automatically created. For example, when we set these options for the
entities in our model, Xcode creates a subclass of NSManagedObject called
Books with the properties title, year, cover, thumbnail, and author, and a subclass
called Authors with the properties name and books. From now on, all we need
to do to store a book in the Persistent Store is to create an instance of the
Books class using the NSManagedObject initializer.

Do It Yourself: Click on the Core Data model file to open the


model. Select the Books entity, open the Data Model Inspector
panel, and make sure that the value of the Codegen option is set to
Class Definition and the name of the class is assigned to the Name
field (Figure 15-17). Repeat the same process for the Authors entity.

IMPORTANT: The subclasses of the NSManagedObject class created to


represent each entity in our model are not visible in Xcode. They are
created internally and automatically modified every time entities or

Asynchronous Access Storing Objects


 

Reading and writing information on a database is a delicate process. The All the interaction between our code and the Persistent Store is done
database may be accessed by different parts of the application and from through the context. When we want to access the objects already stored,
different threads, which can cause errors or even data corruption. To make add new ones, remove them, or modify any of their values, we must do it
sure the data is accurate and safe, we must access the database from the in the context and then move those changes from the context to the
same thread assigned to the Core Data context. For this purpose, the Persistent Store. To illustrate how this process works, we are going to
NSManagedObjectContext class includes the following asynchronous method. create a simple example that allows us to add, delete, and list books.

perform(schedule: ScheduledTaskType, Closure)—This Figure 15-18: Interface to list and add books
asynchronous method performs a closure in the thread assigned to
the Core Data context. The schedule argument determines how the
closure is going to be executed. It is an enumeration with the values
immediate (the closure runs asynchronously), and enqueued (the closure
runs concurrently). If the argument is ignored, the closure is executed
asynchronously.

The interface includes a Table View Controller embedded in a Navigation


Controller and a second scene with a form to add values. The Navigation
Bar of the Table View Controller includes two bar buttons: Delete Book to
delete books and Add Book to add new ones. The Add Book button is
connected to the second scene with a Show segue. This scene includes two
Text Fields to let the user insert the title and year of a new book and a bar
button called Save Book to save it.

Do It Yourself: Delete the initial scene. Add a Table View Controller


to the Storyboard and embed it in a Navigation Controller
(remember to designate the Navigation Controller as the initial view
controller). Add two bar buttons to the Navigation Bar called Delete The purpose of our application is to show the list of objects already stored
Book and Add Book. Add a new scene. Connect the Add Book button in the Persistent Store and create new ones. The initial scene is going to list
to this scene with a Show segue. Add a bar button to the second all the books available, and the second scene will create new objects with
scene called Save Book. Add two labels and two Text Fields to the the values provided by the user. New objects are created from the
NSManagedObject initializer of our subclass (Books) and then the values are
second scene.
assigned to this object's properties. The process is introduced next in the
EditBookViewController class we have created to control the second scene.
The Persistent Store stores the app's data in a database, but we still need
an MVC data model to temporarily store the data for the scenes. Core Data
Listing 15-24: Adding new objects to the Persistent Store
makes this work easy. Core Data objects conform to the Identifiable protocol

and include the following property to provide a unique identifier.
import UIKit
import CoreData
objectID—This property returns the object's unique identifier. It is a
class EditBookViewController: UIViewController {
value of type NSManagedObjectID. @IBOutlet weak var bookTitle: UITextField!
@IBOutlet weak var bookYear: UITextField!
var context: NSManagedObjectContext!
The identifiers are defined by the NSManagedObjectID class, and the objects
are returned as NSManagedObject objects (Books and Authors), so all our model override func viewDidLoad() {
needs to do is to create a diffable data source that works with super.viewDidLoad()
let app = UIApplication.shared
NSManagedObjectID values and provide an array to store the objects retrieved let appDelegate = app.delegate as! AppDelegate
from the database, as shown next. context = appDelegate.persistentContainer.viewContext

bookTitle.becomeFirstResponder()
Listing 15-23: Defining the model to work with Core Data }
 @IBAction func saveBook(_ sender: UIBarButtonItem) {
let title = bookTitle.text!.trimmingCharacters(in: .whitespaces)
import UIKit let year = Int16(bookYear.text!)
import CoreData if title != "" && year != nil {
Task(priority: .high) {
enum Sections { await storeBook(title: title, year: year!)
case main }
} }
struct ApplicationData { }
var dataSourceBooks: UITableViewDiffableDataSource<Sections, NSManagedObjectID>! func storeBook(title: String, year: Int16) async {
var listOfBooks: [Books] = [] await context.perform {
} let newBook = Books(context: self.context)
var AppData = ApplicationData() newBook.title = title
 newBook.year = year
newBook.thumbnail = UIImage(named: "nothumbnail")?.pngData()

do { system automatically assigns the value nil to the rest of the properties (cover
try self.context.save()
self.closeScene() and author).
} catch { The Books() initializer inserts the new object into the context, but this
print("Error: \(error)")
change is not permanent. If we close the app after the values are assigned
}
} to the properties, the object is lost. To persist the changes, we must save
} the context with the save() method. This method takes the information in
func closeScene() {
Task(priority: .high) { the context and modifies the Persistent Store with it, so everything is
await MainActor.run { stored on file.
navigationController?.popViewController(animated: true)
}
} Do It Yourself: Create a Swift file called ApplicationData.swift for
}
}
the model in Listing 15-23. Create a subclass of the UIViewController
 class called EditBookViewController and assign it to the second scene.
Connect the first Text Field with an Outlet called bookTitle and the
As illustrated in this example, the first thing we need to do in any view second Text Field with an Outlet called bookYear. Connect the Save
controller that wants to access Core Data is to get a reference to the
Book button with an Action called saveBook(). Complete the class with
context from the app's delegate. In the view controller of Listing 15-24, we
the code in Listing 15-24.
assigned the context to a property called context for easy access.
When the Save Book button is pressed, the saveBook() method checks if the
The process to get the objects back from the Persistent Store is the
values in the Text Fields are valid and executes an asynchronous method
opposite. Instead of moving the changes in the context to the Persistent
called storeBook() to create the Books object and store it in the Persistent
Store, we get the objects from the Persistent Store and move them into the
Store. This method performs two asynchronous tasks: the perform() method
context. Once the objects are in the context, we can read their properties,
of the Core Data context to safely access the Persistent Store, and the run()
modify the values, or delete them. Core Data defines the NSFetchRequest
method of the Main Actor to remove the scene from the main thread
class to request objects from the Persistent Store. The following are some
when the process is over (see Concurrency in Chapter 14). To store the
of the properties included by the class for configuration.
values in the Persistent Store, we create a new object with the Books()
initializer. This not only creates a new object of type Books but it also adds it
to the context. Next, we assign the values inserted by the user to the
predicate—This property sets or returns the predicate used to filter
object's title and year properties and also a placeholder image called the objects. It is a value of type NSPredicate; a Foundation class used to
nothumbnail to the thumbnail property (the image is available on our establish logical conditions that describe objects in the Persistent
website). The title and year values are assigned directly to the properties, Store.
but the image must be converted to a Data structure with the pngData() sortDescriptors—This property sets or returns an array of sort
method of the UIImage object (see UIImage in Chapter 5). Notice that the descriptors that determine what the order of the objects obtained by
the request should be. It is an array of values of type NSSortDescriptor, a 
Foundation class used to sort the objects according to the value of a import UIKit
import CoreData
property in ascending or descending order.
class BooksViewController: UITableViewController {
fetchLimit—This property sets or returns the maximum number of var context: NSManagedObjectContext!
objects that the request should return. It takes a value of type Int.
override func viewDidLoad() {
resultType—This property sets or returns a value that determines super.viewDidLoad()
let app = UIApplication.shared
the type of data returned by the request. The framework offers the
let appDelegate = app.delegate as! AppDelegate
NSFetchRequestResultType structure with properties to define the value. context = appDelegate.persistentContainer.viewContext
The properties available are managedObjectResultType (it returns values of tableView.register(UITableViewCell.self, forCellReuseIdentifier: "booksCell")
type NSManagedObject), managedObjectIDResultType (it returns the prepareDataSource()
}
identification values of the NSManagedObject objects instead of the override func viewWillAppear(_ animated: Bool) {
objects themselves), dictionaryResultType (it returns a dictionary with the super.viewWillAppear(animated)
Task(priority: .high) {
values of the properties), and countResultType (it returns an integer await loadRequest()
value with the total of objects found).
await MainActor.run {
propertiesToFetch—This property sets or returns an array of values prepareSnapshot()
}
that determine the properties we want to get (by default, all the }
properties of the NSManagedObject objects are returned). The properties }
func prepareDataSource() {
of an entity (attributes) are represented by objects of the AppData.dataSourceBooks = UITableViewDiffableDataSource<Sections, NSManagedObjectID>
NSPropertyDescription class, or subclasses of it. (tableView: tableView) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "booksCell", for: indexPath)
if let item = AppData.listOfBooks.first(where: { $0.objectID == itemID }) {
Every time we want to read objects from the Persistent Store, we must var config = cell.defaultContentConfiguration()
config.text = item.title
create an NSFetchRequest object to determine what type of objects we want. config.secondaryText = item.author?.name ?? "Undefined"
Because the request has to be associated to an entity, the subclasses of the config.secondaryTextProperties.color = .systemGray
NSManagedObject class representing our entities include the fetchRequest()
if let data = item.thumbnail, let image = UIImage(data: data) {
method. This method returns an NSFetchRequest object already associated to config.image = image
the entity. Once the request object is ready, we must fetch the objects with } else {
config.image = UIImage(named: "nothumbnail")
the fetch() method provided by the context. The following is the view }
controller for the Table View of our example. The code performs a request, config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
stores the objects in the model, and shows their values on the screen. cell.contentConfiguration = config
}
return cell
Listing 15-25: Fetching values from the Persistent Store }

} to the object, we show the placeholder image. The result is illustrated


func loadRequest() async {
await context.perform { below.
let request: NSFetchRequest<Books> = Books.fetchRequest()
do {
AppData.listOfBooks = try self.context.fetch(request)
Figure 15-19: Adding books to a Persistent Store
} catch {
print("Error: \(error)")
}
}
}
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>()
snapshot.appendSections([.main])
snapshot.appendItems(AppData.listOfBooks.map({ $0.objectID }))
AppData.dataSourceBooks.apply(snapshot)
}
} 

Do It Yourself: Create a UITableViewController subclass called


There are different places from where we can perform a request,
BooksViewControllerand assign it to the Table View Controller.
depending on the requirements of our application. In this example, we
created an asynchronous method called loadRequest() and then execute it Complete the class with the code in Listing 15-25. Download the
from the viewWillAppear() method, so the objects are retrieved and the table nothumbnail.png image from our website and add it to the Assets
is updated every time the user opens the scene. Catalog. Run the application. The first screen shows an empty Table
To make a request, we first get the NSFetchRequest object by calling the View (Figure 15-19, left). Press the Add Book button and insert the
fetchRequest() method on the subclass of the objects we want to read (in this values of a book (Figure 15-19, center). At the end, the table should
case we want to get books, so we call it on the Books class). Once the look like the right picture in Figure 15-19.
request is ready, we fetch the objects with the fetch() method and assign
them to the listOfBooks property in our model. IMPORTANT: Snapshot updates must always be performed from
The rest of the process is like any other example we have seen so far. The the same thread. This could be the main thread or a background
snapshot gets the identifiers of the Books objects in the listOfBooks property thread, but it must always be the same. This is the reason why in the
and provides them to the diffable data source, and the diffable data source
view controller of Listing 15-25 we call the prepareSnapshot() method
reads the properties of each object and configures the cell to show the
from the Main Actor.
values on the screen. The only difference is in how we process the images.
In this case, the images are stored in the Persistent Store as Data objects, so
The next step is to allow the user to insert the authors. This demands our
we must check whether there is data available or not and then initialize the
application to provide additional scenes where the user can select and add
UIImage object with that data (UIImage(data: data)). If no image was assigned
new objects. For our example, we have decided to expand the interface
with another Table View Controller to list the authors available and an called Save Author to the last scene. Your interface should look like
additional scene to insert more. To provide access to these new scenes, we the combination of Figures 15-18 and 15-20.
have added a button called Select to the EditBookViewController’s scene, along
with a label to show the name of the selected author (Figure 15-20, left). Every time an author is selected or created, we must get its Authors object
and send it back to the EditBookViewController class to assign it to the book.
Figure 15-20: Interface to list and add authors As we have already seen, there are different ways to do it, but for this
example we are going to use Unwind Segues (see Chapter 9, Figure 9-12).
We need two Unwind Segues, one that is triggered when the user selects
an author from the Table View Controller, and another that is triggered
when the user inserts a new author. When the Unwind Segues are
triggered, we must get the Authors object that represents the author and
update the scene with the value of its name property. To receive and
process this information, we must modify the EditBookViewController class to
include the Action for the Unwind Segues and properties to store and
 manage the Authors object.

When the user presses the Add Book button in the initial scene (Figure 15- Listing 15-26: Adding an author to the book
18, center), the next scene now shows three input options: a Text Field to 
insert the title of the book, a Text Field to insert the year, and the Select import UIKit
button to select the author (Figure 15-20, left). This button is connected to import CoreData
the Table View Controller for the authors with a Show segue (Figure 15-20, class EditBookViewController: UIViewController {
center). In addition, we have included a bar button called Add Author to @IBOutlet weak var bookTitle: UITextField!
open a scene that includes a Text Field to insert the name of a new author @IBOutlet weak var bookYear: UITextField!
@IBOutlet weak var authorName: UILabel!
(Figure 15-20, right). var context: NSManagedObjectContext!
var selectedAuthor: Authors!
Do It Yourself: Add two labels and a button called Select to the override func viewDidLoad() {
EditBookViewController’s
scene (Figure 15-20, left). Add a Table View super.viewDidLoad()
let app = UIApplication.shared
Controller to the Storyboard and connect the Select button to this let appDelegate = app.delegate as! AppDelegate
scene with a Show segue (Figure 15-20, center). Add a bar button context = appDelegate.persistentContainer.viewContext

called Add Author to this scene. Add a new scene to the Storyboard bookTitle.becomeFirstResponder()
and connect the Add Author button to this scene with a Show segue }
@IBAction func saveBook(_ sender: UIBarButtonItem) {
(Figure 15-20, right). Add a label, an input field, and a bar button let title = bookTitle.text!.trimmingCharacters(in: .whitespaces)
let year = Int16(bookYear.text!)

if title != "" && year != nil { scenes added to the interface in Figure 15-20, the backAuthor() method is
Task(priority: .high) {
await storeBook(title: title, year: year!) executed (due to the Unwind Segues). In this method, the code gets the
} Authors object that represents the author, assigns it to the selectedAuthor
}
}
property, and updates the label on the screen with the value of its name
func storeBook(title: String, year: Int16) async { property. (This process is done for each Unwind Segue, so no matter if the
await context.perform { author is selected from the table or inserted in the form, its name is always
let newBook = Books(context: self.context)
newBook.title = title shown on the screen and the Authors object is assigned to the book when
newBook.year = year the user presses the Save Book button.)
newBook.author = self.selectedAuthor
The process to list and create new Authors objects is the same we used for
do { books. We perform a request to get the Authors objects from the Persistent
try self.context.save() Store and store them in a property in the model. The following are the
self.closeScene()
} catch { properties we must add to the model to manage this information.
print("Error: \(error)")
}
} Listing 15-27: Defining a model to manage books and authors
} 
func closeScene() {
import UIKit
Task(priority: .high) {
import CoreData
await MainActor.run {
navigationController?.popViewController(animated: true)
enum Sections {
}
case main
}
}
}
struct ApplicationData {
@IBAction func backAuthor(_ segue: UIStoryboardSegue) {
var dataSourceBooks: UITableViewDiffableDataSource<Sections, NSManagedObjectID>!
if segue.identifier == "backFromList" {
var dataSourceAuthors: UITableViewDiffableDataSource<Sections, NSManagedObjectID>!
let controller = segue.source as! AuthorsViewController
selectedAuthor = controller.selectedAuthor
var listOfBooks: [Books] = []
authorName.text = selectedAuthor.name
var listOfAuthors: [Authors] = []
} else if segue.identifier == "backFromNew" {
}
let controller = segue.source as! EditAuthorViewController
var AppData = ApplicationData()
selectedAuthor = controller.selectedAuthor

authorName.text = selectedAuthor.name
}
} With the model ready, we can now load and show the authors to the user.
}
 The following is the view controller to manage the Table View Controller
added to the interface for this purpose (see Figure 15-20). We call it
This view controller manages the scene that allows the user to insert new AuthorsViewController.
books, but it also must process the author selected by the user and assign
it to the book. When the user selects or inserts a new author from the Listing 15-28: Listing authors
 }
}
import UIKit }
import CoreData func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>()
class AuthorsViewController: UITableViewController { snapshot.appendSections([.main])
var context: NSManagedObjectContext! snapshot.appendItems(AppData.listOfAuthors.map({ $0.objectID }))
var selectedAuthor: Authors! AppData.dataSourceAuthors.apply(snapshot, animatingDifferences: false)
}
override func viewDidLoad() { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
super.viewDidLoad() if let itemID = AppData.dataSourceAuthors.itemIdentifier(for: indexPath) {
let app = UIApplication.shared if let item = AppData.listOfAuthors.first(where: { $0.objectID == itemID }) {
let appDelegate = app.delegate as! AppDelegate selectedAuthor = item
context = appDelegate.persistentContainer.viewContext }
performSegue(withIdentifier: "backFromList", sender: self)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "authorsCell") }
prepareDataSource() }
} }
override func viewWillAppear(_ animated: Bool) { 
super.viewWillAppear(animated)
Task(priority: .high) {
await loadRequest() Other than changing the name of the array (listOfAuthors), the rest of the
code, including the fetch request, is the same we used to list books. The
await MainActor.run {
prepareSnapshot() code also implements the protocol method tableView(UITableView,
} didSelectRowAt:) to assign the author selected by the user to the selectedAuthor
}
} property and trigger the Unwind Segue (the example assumes that we
func prepareDataSource() { have created an Unwind Segue from the Table View Controller to the
AppData.dataSourceAuthors = UITableViewDiffableDataSource<Sections,
backAuthor() Action and have identified the segue with the string
NSManagedObjectID>(tableView: tableView) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "authorsCell", for: indexPath) "backFromList".
if let item = AppData.listOfAuthors.first(where: { $0.objectID == itemID }) { The view controller to add new authors is also very similar to the one we
var config = cell.defaultContentConfiguration()
config.text = item.name used to add new books. We have to take the text inserted by the user in
cell.contentConfiguration = config the Text Field, create the new Authors object to represent the author, save
}
return cell
the context, and finally trigger the Unwind Segue to move back to the
} EditBookViewController’s scene (again, the example assumes that we have
} created an Unwind Segue from this scene to the backAuthor() Action
func loadRequest() async {
await context.perform { identified with the string "backFromNew"). We called this view controller
let request: NSFetchRequest<Authors> = Authors.fetchRequest() EditAuthorViewController.
do {
AppData.listOfAuthors = try self.context.fetch(request)
} catch { Listing 15-29: Inserting new authors
print("Error: \(error)")

 

import UIKit
import CoreData With these additions, our basic app is complete. When we pressed the
Select button, the app opens a Table View with all the authors available
class EditAuthorViewController: UIViewController {
@IBOutlet weak var authorName: UITextField! (Figure 15-21, center). If there are no authors or the author we want is not
var context: NSManagedObjectContext! on the list, we can press the Add Author button and insert a new one
var selectedAuthor: Authors!
(Figure 15-21, right). Every time we select an author from the list or insert
override func viewDidLoad() { a new one, the app goes back to the scene with the book’s information and
super.viewDidLoad()
shows the name of the author on the screen (Figure 15-21, left). When the
let app = UIApplication.shared
let appDelegate = app.delegate as! AppDelegate book is saved, the Authors object that represents the author is assigned to
context = appDelegate.persistentContainer.viewContext the book’s author property and therefore the name of the author is now
authorName.becomeFirstResponder() shown on the list of books.
}
@IBAction func saveAuthor(_ sender: UIBarButtonItem) {
let name = authorName.text!.trimmingCharacters(in: .whitespaces)
Figure 15-21: Assigning an author to a book
if name != "" {
Task(priority: .high) {
await storeAuthor(name: name)
}
}
}
func storeAuthor(name: String) async {
await context.perform {
self.selectedAuthor = Authors(context: self.context)
self.selectedAuthor.name = name

do { 
try self.context.save()
self.closeScene()
} catch { Do It Yourself: Update the model with the code in Listing 15-27.
print("Error: \(error)")
}
Update the EditBookViewController class with the code in Listing 15-26.
} Connect the label that is going to show the name of the author to
}
func closeScene() { the authorName Outlet. Create a UITableViewController subclass called
Task(priority: .high) { AuthorsViewController and assign it to the new Table View Controller
await MainActor.run {
performSegue(withIdentifier: "backFromNew", sender: self) (Figure 15-20, center). Complete the class with the code in Listing
} 15-28. Create an Unwind Segue to the backAuthor() method for this
}
} scene with the identifier "backFromList". Create a UIViewController
}
subclass called EditAuthorViewController and assign it to the last scene Counting Objects
(Figure 15-20, right). Connect the Text Field in this scene with an

Outlet called authorName and complete the EditAuthorViewController class
with the code in Listing 15-29. Create an Unwind Segue to the The NSManagedObjectContext class includes the count() method to count the
backAuthor() method for this scene with the identifier number of objects in a request. The method returns an integer with the
"backFromNew". Run the application and press the Add New Book number of objects we would get if we call the fetch() method with the same
button. Press the Select button and select or insert a new author. request. For example, we can use it in the EditBookViewController class to get
Insert the rest of the information and save the book. You should see the number of authors already available. The request is always the same,
the list of books with their respective authors. but instead of fetching the objects we count them with the count() method.

Listing 15-30: Counting the authors available



override func viewDidLoad() {
super.viewDidLoad()
let app = UIApplication.shared
let appDelegate = app.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext

bookTitle.becomeFirstResponder()

Task(priority: .background) {
await context.perform {
let request: NSFetchRequest<Authors> = Authors.fetchRequest()
if let total = try? self.context.count(for: request) {
print("Total Authors: \(total)")
}
}
}
}

Do It Yourself: Replace the viewDidLoad() method in the


class with the method in Listing 15-30. Run the
EditBookViewController
application and press the Add Book button. You should see the total
number of authors available in the Persistent Store printed on the
console.

If what we want is to get the number of objects associated to a To-Many Predicates


relationship, we can count the number of items returned by the property

that represents the relationship. For example, we can count the number of
books of every author and show it next to the name.
The requests performed in previous examples are getting all the objects
associated to an entity. The Core Data framework defines the NSPredicate
Listing 15-31: Counting the books of each author
class to fetch only the objects that comply to certain conditions. For

example, we could get only the books that were published in the year
func prepareDataSource() {
AppData.dataSourceAuthors = UITableViewDiffableDataSource<Sections, NSManagedObjectID> 1983. The class defines the following initializer to create a predicate with
(tableView: tableView) { tableView, indexPath, itemID in all the conditions we need.
let cell = tableView.dequeueReusableCell(withIdentifier: "authorsCell", for: indexPath)
if let item = AppData.listOfAuthors.first(where: { $0.objectID == itemID }) {
var config = cell.defaultContentConfiguration() NSPredicate(format: String, argumentArray: [Any]?)—This
let name = item.name ?? "Undefined"
initializer creates a NSPredicate object with the conditions set by the
var total = 0 format argument. The argumentArray argument is an optional array
if let totalBooks = item.books {
total = totalBooks.count of values that replace placeholders in the string assigned to the
} format argument. The argumentArray argument may be ignored or
config.text = "\(name) (\(total))"
replaced by a list of values separated by commas.
cell.contentConfiguration = config
}
return cell To filter values in a request, we must create the NSPredicate object and
} assign it to the request’s predicate property, as shown next.
}

Listing 15-32: Filtering books by year
The prepareDataSource() method in Listing 15-31 replaces the same method in 

the AuthorsViewController class of Listing 15-28. Instead of assigning just the func loadRequest() async {
await context.perform {
name of the author to the cell’s label, we count the books associated to let request: NSFetchRequest<Books> = Books.fetchRequest()
the author and create a string with this value and the author's name. Now request.predicate = NSPredicate(format: "year = 1983")
do {
the user can see how many books are available per author. AppData.listOfBooks = try self.context.fetch(request)
} catch {
print("Error: \(error)")
}
}
}

This example defines an NSPredicate object for the BooksViewController class to print("Error: \(error)")
}
search for books published in 1983 (the year property is equal to 1983). }
If we are trying to search for a value in a relationship, we can concatenate }

the properties with dot notation. For example, the following request looks
for books written by the author Stephen King.
We may include all the placeholders and arguments we need. The
placeholders are replaced by the arguments one by one, in consecutive
Listing 15-33: Filtering books by author
order, as with formatted strings (see String structures in Chapter 3). For

example, we can search for books of a particular author and year. (Notice
func loadRequest() async {
await context.perform { that since the value of the year property is an integer, we use the %d
let request: NSFetchRequest<Books> = Books.fetchRequest() placeholder.)
request.predicate = NSPredicate(format: "author.name = 'Stephen King'")
do {
AppData.listOfBooks = try self.context.fetch(request) Listing 15-35: Creating filters with multiple values
} catch { 
print("Error: \(error)")
} func loadRequest() async {
} let search = "Stephen King"
} let year = 1983

await context.perform {
let request: NSFetchRequest<Books> = Books.fetchRequest()
The name of the author was inserted inside the string using single quotes, request.predicate = NSPredicate(format: "author.name = %@ && year = %d", search,
but we do not always know beforehand what value we need to search for. year)
do {
To incorporate the value of a variable inside the formatted string, we can AppData.listOfBooks = try self.context.fetch(request)
use placeholders. Placing the characters %@ inside the string, for example, } catch {
print("Error: \(error)")
indicates to the initializer that the value after the comma goes in that }
place. }
}

Listing 15-34: Creating filters with placeholders

Do It Yourself: Replace the viewWillAppear() method in the
func loadRequest() async {
let search = "Stephen King" class with any of the methods introduced above to
BooksViewController
see how predicates work. Add books with different authors and
await context.perform {
let request: NSFetchRequest<Books> = Books.fetchRequest() years to test them.
request.predicate = NSPredicate(format: "author.name = %@", search)
do {
AppData.listOfBooks = try self.context.fetch(request)
} catch {

IMPORTANT: The placeholder %@ is replaced by the value


specified in the arguments between quotes. If you need to add the Listing 15-36: Filtering values with predicate keywords
value to the predicate without the quotes, you must use the 

placeholder %K instead (called Dynamic Key). func loadRequest() async {


let search = "stephen"

Predicates use comparison and logical operators like those offered by Swift. await context.perform {
let request: NSFetchRequest<Books> = Books.fetchRequest()
For example, we can compare values with the operators =, !=, >, <, >= and request.predicate = NSPredicate(format: "author.name BEGINSWITH[c] %@", search)
<=, and also concatenate conditions with the characters && (or the word do {
AppData.listOfBooks = try self.context.fetch(request)
AND), || (or the word OR) and ! (or the word NOT). Predicates also include
} catch {
keywords for a more precise search. The following are the most frequently print("Error: \(error)")
used. }
}
}
BEGINSWITH—The condition determined by this keyword is true 

when the expression on the left begins with the expression on the
IMPORTANT: Diacritics are the small marks used in some languages
right.
to change the pronunciation of a letter, like the visible stress over
CONTAINS—The condition determined by this keyword is true when Spanish vowels. When we specify the character d between square
the expression on the left contains the expression on the right. brackets, the search ignores these marks and looks for the letter in
ENDSWITH—The condition determined by this keyword is true when its basic form. The c and d characters are usually implemented
the expression on the left ends with the expression on the right. together, as in [cd].
LIKE—The condition determined by this keyword is true when the
A practical application of predicates is to check for duplicates. Storing
expression on the left is equal to the expression on the right.
duplicated values is something that all applications should be prepared to
IN—The condition determined by this keyword is true when the avoid. For example, if we open our application and insert an author that
expression on the left is equal to any of the values included in the already exists, two Authors objects with the same name will be stored in the
expression on the right. The values are provided as an array between Persistent Store. To avoid this situation, we can use a request with a
parentheses. predicate that searches for authors of the same name before creating a
new object. The following example modifies the storeAuthor() method of the
These keywords may be accompanied by the characters c or d between EditAuthorViewController class to avoid duplicates.

square brackets to specify a case insensitive or diacritic insensitive search.


For example, we may search for authors with a name beginning with the Listing 15-37: Checking for duplicates
word "stephen", without considering uppercase or lowercase letters. 
func storeAuthor(name: String) async {
await context.perform { Sort Descriptors
let request: NSFetchRequest<Authors> = Authors.fetchRequest()
request.predicate = NSPredicate(format: "name = %@", name) 
if let total = try? self.context.count(for: request), total <= 0 {
self.selectedAuthor = Authors(context: self.context) Objects returned from a request are usually in the order they were
self.selectedAuthor.name = name
created, but this is not guaranteed. To sort the objects in a specific order,
do { we can associate the request with an object of type NSSortDescriptor. This
try self.context.save()
self.closeScene()
class creates objects that specify an order according to the values of a
} catch { property. The sorting criteria is determined by methods provided by the
print("Error: \(error)") values themselves. For example, data types like Date implement only the
}
} compare() method to compare their values, but the NSString class implements
} other methods that allow us to do things like order strings without
}

differentiating between lowercase and uppercase letters (see NSString in
Chapter 4). By default, an NSSortDescriptor object uses the compare() method
The method in Listing 15-37 creates a request for the Authors entity and to compare any type of value, but we can specify others from the initializer.
uses the value inserted in the Text Field to create a predicate. The
predicate looks for objects with the name equal to the value of the name NSSortDescriptor(key: String?, ascending: Bool, selector:
constant. In the next statement, we call the count() method with this Selector?)—This initializer creates an NSSortDescriptor object that
request to get the number of objects that match the condition. If the value orders the objects according to the value of the property specified by
returned is 0, we know that there are no authors in the Persistent Store the key argument. This argument is a string with the name of the
with that name and can create and add a new one. property, the ascending argument determines if the objects are
sorted in ascending or descending order, and the selector argument is
Do It Yourself: Replace the storeAuthor() method in the a method that determines the sorting criteria.
EditAuthorViewControllerclass with the method in Listing 15-37. Run the
application and try to insert an author that already exists. Use the The FetchRequest class includes the sortDescriptors property to define the order
same filter in the EditBookViewController class to avoid duplicates when of a request. All we need to do is to create at least one NSSortDescriptor
inserting new books. You can create a predicate that checks for object and assign it to this property. The following example sorts the Books
books with the same title and author (the placeholder characters objects by title in ascending order.
%@ can take any type of values and objects, including
NSManagedObject objects like Books and Authors). Listing 15-38: Sorting the books by title

func loadRequest() async {
await context.perform {

let request: NSFetchRequest<Books> = Books.fetchRequest()


let sort = NSSortDescriptor(key: "title", ascending: true)
request.sortDescriptors = [sort] Listing 15-40: Sorting by title without differentiating between lowercase
do { and uppercase letters
AppData.listOfBooks = try self.context.fetch(request)

} catch {
print("Error: \(error)") func loadRequest() async {
} await context.perform {
} let request: NSFetchRequest<Books> = Books.fetchRequest()
} let sort = NSSortDescriptor(key: "title", ascending: true, selector:
 #selector(NSString.caseInsensitiveCompare(_:)))
request.sortDescriptors = [sort]
do {
The sortDescriptors property takes an array of NSSortDescriptor objects, so we AppData.listOfBooks = try self.context.fetch(request)
can specify different conditions to sort the list. The order is established } catch {
print("Error: \(error)")
according to the location of the objects in the array. For example, we can }
sort the books by author first and then by year. }
}

Listing 15-39: Sorting books by author and year

Do It Yourself: Replace the loadRequest() method in the
func loadRequest() async {
await context.perform {
class with any of the methods introduced above to
BooksViewController
let request: NSFetchRequest<Books> = Books.fetchRequest() see how sort descriptors work. Change the value of the ascending
let sort1 = NSSortDescriptor(key: "author.name", ascending: true)
let sort2 = NSSortDescriptor(key: "year", ascending: true) argument to see how the order changes in each case.
request.sortDescriptors = [sort1, sort2]
do {
AppData.listOfBooks = try self.context.fetch(request)
} catch {
print("Error: \(error)")
}
}
}

By default, NSSortDescriptor objects use the compare() method to sort the


values, but we can implement others. These methods must be assigned to
the selector argument of the NSSortDescriptor initializer. The most frequently
used is caseInsensitiveCompare() because it compares values without
differentiating between lowercase and uppercase letters. The following
example implements this method to sort the books by title.
Modifying Objects to allow the user to edit its values. The following are the methods we need
in the BooksViewController class for this purpose.

Listing 15-41: Sending the book selected by the user
Modifying objects in the Persistent Store is easy. We just need to get the

object from the Persistent Store, assign the new values to the object's
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
properties, and save the context. In our application, this means letting the performSegue(withIdentifier: "showEditBook", sender: self)
user select a book and provide a form to allow him to modify the title and }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
year. For this purpose, we can use the same scene designed to insert new if segue.identifier == "showEditBook" {
books. All we need is an additional Show segue, as shown next. if let path = tableView.indexPathForSelectedRow {
if let itemID = AppData.dataSourceBooks.itemIdentifier(for: path) {
if let item = AppData.listOfBooks.first(where: { $0.objectID == itemID }) {
Figure 15-22: Interface to modify books let controller = segue.destination as! EditBookViewController
controller.selectedBook = item
}
}
}
}
}

The protocol method performs the segue when a cell is selected by the
user, and the prepare() method gets the index path of the selected row, uses
this value to get the Books object from the model, and assigns it to a
property in the EditBookViewController class called selectedBook.
The EditBookViewController class must receive this object and show its values
to the user. But we must also contemplate that the user might be trying to
insert a new book instead. The following are all the changes we need to
 introduce to this class to respond to both situations.

The interface in Figure 15-22 includes a Show segue from the Table View Listing 15-42: Editing a book
Controller to the second scene identified with the name "showEditBook". 

When the user selects a cell, we must perform this segue and send the import UIKit
import CoreData
Books object representing the book selected by the user from the
BooksViewController view controller to the EditBookViewController view controller class EditBookViewController: UIViewController {
@IBOutlet weak var bookTitle: UITextField!
@IBOutlet weak var bookYear: UITextField!

@IBOutlet weak var authorName: UILabel! }


var context: NSManagedObjectContext! await MainActor.run {
var selectedAuthor: Authors! if let book = self.selectedBook {
var selectedBook: Books! var currentSnapshot = AppData.dataSourceBooks.snapshot()
currentSnapshot.reloadItems([book.objectID])
override func viewDidLoad() { AppData.dataSourceBooks.apply(currentSnapshot)
super.viewDidLoad() }
let app = UIApplication.shared self.closeScene()
let appDelegate = app.delegate as! AppDelegate }
context = appDelegate.persistentContainer.viewContext }
func closeScene() {
bookTitle.becomeFirstResponder() navigationController?.popViewController(animated: true)
}
if selectedBook != nil { @IBAction func backAuthor(_ segue: UIStoryboardSegue) {
bookTitle.text = selectedBook.title if segue.identifier == "backFromList" {
bookYear.text = String(selectedBook.year) let controller = segue.source as! AuthorsViewController
authorName.text = selectedBook.author?.name ?? "Undefined" selectedAuthor = controller.selectedAuthor
authorName.text = selectedAuthor.name
selectedAuthor = selectedBook.author } else if segue.identifier == "backFromNew" {
} let controller = segue.source as! EditAuthorViewController
} selectedAuthor = controller.selectedAuthor
@IBAction func saveBook(_ sender: UIBarButtonItem) { authorName.text = selectedAuthor.name
let title = bookTitle.text!.trimmingCharacters(in: .whitespaces) }
let year = Int16(bookYear.text!) }
if title != "" && year != nil { }
Task(priority: .high) { 
await storeBook(title: title, year: year!)
}
} The scene for the view controller in Listing 15-42 opens when the user taps
} the Add Book button or when a cell is selected. What allows us to
func storeBook(title: String, year: Int16) async {
await context.perform {
distinguish one situation from the other is the value of the selectedBook
if self.selectedBook != nil { property. If this property is nil, it means that the user wants to insert a new
self.selectedBook.title = title book, but when the property contains a Books object, it means that the user
self.selectedBook.year = year
self.selectedBook.author = self.selectedAuthor selected a book and wants to edit its values. We first check the value of
} else { this property in the viewDidLoad() method. If it is different from nil, we show
let newBook = Books(context: self.context)
newBook.title = title the book's values on the screen and assign the book's author to the
newBook.year = year selectedAuthor property, otherwise the fields are left empty for the user to
newBook.author = self.selectedAuthor
}
insert a new book.
do { When the user taps the Save Book button to save the book, we check the
try self.context.save() value of the selectedBook property again. If it is different from nil, we modify
} catch {
print("Error: \(error)") the values of the Books object in the selectedBook property with those
}
inserted by the user, otherwise, we create a new Books object. This way, the Deleting Objects
scene fulfills two purposes: it allows the user to edit the values of an

existent book or add a new one.
Notice that the snapshot considers the items that are added or removed
The interface introduced in Figure 15-18 includes a bar button called
from the model, but it does not update their values. Therefore, after the
Delete Book to activate the table’s edition mode. To delete an
user modifies the values of a book, we must call the reloadItems() method on
NSManagedObject object, the NSManagedObjectContext class offers the delete()
the current snapshot to update the item. (We call this method in the Main
method. The method takes a reference to the NSManagedObject object we
Actor to avoid thread conflicts, as explained before.)
want to delete and removes it from the context. As always, the change is
only performed in the context, so we must save it, or the object will not be
Do It Yourself: Create a Show segue from the Table View Controller
deleted from the Persistent Store.
to the second scene and identify it with the name "showEditBook"
As seen in Chapter 10, the edition mode is activated by the setEditing()
(Figure 15-22). Add the methods in Listing 15-41 to the method of the UITableView object (see Listings 10-23), but the values are
BooksViewController class. Update the EditBookViewController class with processed by delegate methods defined in the UITableViewDataSource
the code in Listing 15-42. Run the application and select a book. You protocol, so we must create our own subclass of the
should see the values of the book on the screen. Modify the book's UITableViewDiffableDataSource class (see Listing 10-24).
title and press the Save Book button. The changes should appear on Because we are going to modify the request and the snapshot from
the Table View. different objects (the view controller and the diffable data source) we must
rethink our programming pattern. In our example, this means moving the
loadRequest() and prepareSnapshot() methods from the view controller to the
model, as shown next.

Listing 15-43: Defining the request and the snapshot from the model

import UIKit
import CoreData

enum Sections {
case main
}
class ApplicationData {
var dataSourceBooks: MyDataSource!
var dataSourceAuthors: UITableViewDiffableDataSource<Sections, NSManagedObjectID>!

var listOfBooks: [Books] = []


var listOfAuthors: [Authors] = []

func loadRequestBooks(context: NSManagedObjectContext) async { }


await context.perform { override func tableView(_ tableView: UITableView, commit editingStyle:
let request: NSFetchRequest<Books> = Books.fetchRequest() UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let sort = NSSortDescriptor(key: "title", ascending: true, selector: if editingStyle == .delete {
#selector(NSString.caseInsensitiveCompare(_:))) if let itemID = AppData.dataSourceBooks.itemIdentifier(for: indexPath) {
request.sortDescriptors = [sort] let app = UIApplication.shared
do { let appDelegate = app.delegate as! AppDelegate
self.listOfBooks = try context.fetch(request) let context = appDelegate.persistentContainer.viewContext
} catch {
print("Error") if let item = AppData.listOfBooks.first(where: { $0.objectID == itemID }) {
} Task(priority: .high) {
} await deleteBook(context: context, item: item)
} }
func prepareSnapshotBooks() { }
var snapshot = NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>() }
snapshot.appendSections([.main]) }
snapshot.appendItems(self.listOfBooks.map({ $0.objectID })) }
dataSourceBooks.apply(snapshot) func deleteBook(context: NSManagedObjectContext, item: Books) async {
} await context.perform {
} context.delete(item)
var AppData = ApplicationData() do {
 try context.save()
} catch {
print("Error: \(error)")
To make sure that there is no confusion on what the purpose of the }
methods is, we renamed them to loadRequestBooks() and prepareSnapshotBooks(). }
await AppData.loadRequestBooks(context: context)
The code is the same, but now the loadRequestBooks() method includes a
parameter to receive a reference to the Core Data context to be able to await MainActor.run {
perform the request. AppData.prepareSnapshotBooks()
}
The property to store the diffable data source for the books was defined of }
type MyDataSource. This is the subclass of the UITableViewDiffableDataSource class }

we need to implement the protocol methods.
The subclass in Listing 15-44 implements the two delegate methods we
Listing 15-44: Implementing the protocol methods to delete a book
need; one to tell the Table View that the user is allowed to edit the rows,

and the other to process the removal of the object (see Chapter 10). When
import UIKit
import CoreData the user taps on the Delete button on a row, the second method is called,
we get a reference to the Books object from the model and call an
class MyDataSource: UITableViewDiffableDataSource<Sections, NSManagedObjectID> {
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
asynchronous method to delete it. After the object is deleted, we proceed
{ to save the context and update the request and the snapshot by calling the
return true methods in the model.
The BooksViewController view controller must include an Action for the button config.image = UIImage(named: "nothumbnail")
}
to turn the table's edition mode on and off and use our MyDataSource class config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
to create the diffable data source. The rest of the code remains the same. cell.contentConfiguration = config
}
return cell
Listing 15-45: Deleting a book }
 }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
import UIKit performSegue(withIdentifier: "showEditBook", sender: self)
import CoreData }
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
class BooksViewController: UITableViewController { if segue.identifier == "showEditBook" {
var context: NSManagedObjectContext! if let path = tableView.indexPathForSelectedRow {
if let itemID = AppData.dataSourceBooks.itemIdentifier(for: path) {
override func viewDidLoad() { if let item = AppData.listOfBooks.first(where: { $0.objectID == itemID }) {
super.viewDidLoad() let controller = segue.destination as! EditBookViewController
let app = UIApplication.shared controller.selectedBook = item
let appDelegate = app.delegate as! AppDelegate }
context = appDelegate.persistentContainer.viewContext }
}
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "booksCell") }
prepareDataSource() }
} @IBAction func editBooks(_ sender: UIBarButtonItem) {
override func viewWillAppear(_ animated: Bool) { let editing = !tableView.isEditing
super.viewWillAppear(animated) tableView.setEditing(editing, animated: true)
Task(priority: .high) { }
await AppData.loadRequestBooks(context: context) }

await MainActor.run {
AppData.prepareSnapshotBooks()
} Do It Yourself: Update the ApplicationData structure with the code in
}
Listing 15-43. Create a new file called MyDataSource.swift for the
}
func prepareDataSource() { class in Listing 15-44. Update the BooksViewController class with the
AppData.dataSourceBooks = MyDataSource(tableView: tableView) { tableView, indexPath,
itemID in
code in Listing 15-45 and connect the Delete Book button on the
let cell = tableView.dequeueReusableCell(withIdentifier: "booksCell", for: indexPath) interface with the editBooks() Action. Run the application, press the
if let item = AppData.listOfBooks.first(where: { $0.objectID == itemID }) {
var config = cell.defaultContentConfiguration()
Delete Book button, and press the Delete button. The book should
config.text = item.title be removed from the list.
config.secondaryText = item.author?.name ?? "Undefined"
config.secondaryTextProperties.color = .systemGray
IMPORTANT: If you need to delete several objects in the same
if let data = item.thumbnail, let image = UIImage(data: data) {
config.image = image action, you should call the save() method at the end to improve
} else {

performance. This also applies to other operations with the context. Fetched Results Controller
The context should be saved only after all the operations are

performed.
Storing the results of a request in an array is not recommended in most
situations. Tables and Collection Views can handle thousands of items and
putting all those items in an array might consume too many resources. The
solution is to get only the objects the interface needs at a particular
moment, but this is error prone and demands our code to keep track of the
elements already loaded and request only those that we do not have.
Programming an application to do this work efficiently is difficult. For this
reason, Core Data offers the NSFetchedResultsController class. This class
provides highly optimized code that intermediates between the app and
the Persistent Store; taking care of fetching the objects the interface needs
and updating the list of objects available when some are modified, added,
or removed. To create the controller, the class provides the following
initializer.

NSFetchedResultsController(fetchRequest: NSFetchRequest,
managedObjectContext: NSManagedObjectContext,
sectionNameKeyPath: String?, cacheName: String?)—This
initializer creates an NSFetchedResultsController object that fetches
NSManagedObject objects from the Persistent Store. The fetchRequest
argument is an NSFetchRequest object with the request we want the
controller to use to fetch the objects, the managedObjectContext
argument is a reference to the Core Data context, the
sectionNameKeyPath argument identifies the name of the property used
to create the table’s sections, and the cacheName argument defines the
name of the file used to cache the objects returned by the request.

When working with an NSFetchedResultsController object, we must ask this


object for any information we need about the request (the objects, their
location, etc.). The class offers the following properties and methods to method to update the snapshot and in consequence the values on the
access these values. screen.

fetchRequest—This property returns a reference to the controller(NSFetchedResultsController,


NSFetchRequest object that defines the request used by the controller. didChangeContentWith: NSDiffableDataSourceSnapshot)—
fetchedObjects—This property returns an array with the This method is called on the delegate when the content managed by
the NSFetchedResultsController object has changed. The method receives a
NSManagedObjects currently fetched by the controller.
snapshot that we must apply to the diffable data source to update the
sections—This property returns an array of objects with information
interface.
about the sections. The objects implement the properties defined in
the NSFetchedResultsSectionInfo protocol to provide this information.
There are a few changes we must introduce to our previous example to
sectionIndexTitles—This property returns an array of strings with implement a NSFetchedResultsController object. The first thing we need is a
the titles for the index of every section. property in the model to store this object.
performFetch()—This method executes the controller's fetch
request. The controller does not return any value until this method is Listing 15-46: Updating the model to work with a Fetched Results
called. Controller

object(at: IndexPath)—This method returns the NSManagedObject import UIKit
object at the index path specified by the at argument. import CoreData

indexPath(forObject: ResultType)—This method returns the index enum Sections {


case main
path of the NSManagedObject object specified by the forObject
}
argument. The value returned is an IndexPath structure. struct ApplicationData {
var dataSourceBooks: MyDataSource!
var dataSourceAuthors: UITableViewDiffableDataSource<Sections, NSManagedObjectID>!
After the NSFetchedResultsController object is initialized, we must call the
performFetch() method to perform the request. The results produced by the var fetchedController: NSFetchedResultsController<Books>!
var listOfAuthors: [Authors] = []
request are stored in a temporary container and automatically updated by }
the NSFetchedResultsController object every time a modification is introduced var AppData = ApplicationData()

(an object is modified, deleted, moved, or new objects are added).
Because of the close relationship between this controller and the views, it
The fetchedController property replaces the previous listOfBooks property (the
is important to make sure that all the changes are immediately reflected on
Books objects are not going to be stored in an array but instead provided by
the interface. Core Data simplifies this task with the addition of the
the NSFetchedResultsController object). Notice that we have also removed the
NSFetchedResultsControllerDelegate protocol. This protocol defines the following

methods to create the request and the snapshot because now everything super.viewWillAppear(animated)
do {
is managed by the Fetched Results Controller. try AppData.fetchedController.performFetch()
The Fetched Results Controller is created from NSManagedObject objects } catch {
print("Error")
(Books and Authors), but the diffable data source and the snapshots are }
created with their NSManagedObjectID values. To get the NSManagedObject }
func prepareDataSource() {
object from these identifiers, the NSManagedObjectContext class includes the AppData.dataSourceBooks = MyDataSource(tableView: tableView) { tableView, indexPath,
following methods. itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "booksCell", for: indexPath)
if let item = try? self.context.existingObject(with: itemID) as? Books {
existingObject(with: NSManagedObjectID)—This method var config = cell.defaultContentConfiguration()
returns the object identified with the identifier specified by the with config.text = item.title
config.secondaryText = item.author?.name ?? "Undefined"
argument or nil if no object is found in the context. config.secondaryTextProperties.color = .systemGray

object(with: NSManagedObjectID)—This method returns the if let data = item.thumbnail, let image = UIImage(data:data){
config.image = image
object identified with the identifier specified by the with argument. If } else {
the object is not found in the context, it is fetched from the Persistent config.image = UIImage(named: "nothumbnail")
}
Store. config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
cell.contentConfiguration = config
The following are the changes required in the BooksViewController class to get }
the Books objects from a Fetched Results Controller. return cell
}
}
Listing 15-47: Working with a Fetched Results Controller func prepareFetchedController() {
let request: NSFetchRequest<Books> = Books.fetchRequest()
 let sort = NSSortDescriptor(key: "title", ascending: true, selector:
import UIKit #selector(NSString.caseInsensitiveCompare(_:)))
import CoreData request.sortDescriptors = [sort]
AppData.fetchedController = NSFetchedResultsController(fetchRequest: request,
class BooksViewController: UITableViewController, NSFetchedResultsControllerDelegate { managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
var context: NSManagedObjectContext! AppData.fetchedController.delegate = self
}
override func viewDidLoad() { func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
super.viewDidLoad() didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
let app = UIApplication.shared let newsnapshot = snapshot as NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>
let appDelegate = app.delegate as! AppDelegate AppData.dataSourceBooks.apply(newsnapshot, animatingDifferences: true)
context = appDelegate.persistentContainer.viewContext }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "booksCell")
performSegue(withIdentifier: "showEditBook", sender: self)
prepareDataSource()
}
prepareFetchedController()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
if segue.identifier == "showEditBook" {
override func viewWillAppear(_ animated: Bool) {
if let path = tableView.indexPathForSelectedRow {
if let itemID = AppData.dataSourceBooks.itemIdentifier(for: path) { Because this process is required every time there are changes in the
if let item = try? context.existingObject(with: itemID) as? Books {
let controller = segue.destination as! EditBookViewController context, we call it in the viewWillAppear() method to make sure that every
controller.selectedBook = item time the user opens this scene, the Fetched Results Controller is updated.
}
}
The diffable data source is the same as before, except this time instead of
} getting the object from an array in the model we call the existingObject(with:)
}
method with the identifier received by the closure to get the object from
}
@IBAction func editBooks(_ sender: UIBarButtonItem) { the context. This returns the Books object associated to that identifier that
let editing = !tableView.isEditing we can use to configure the cell.
tableView.setEditing(editing, animated: true)
} We do the same in the prepare() method. When the user selects a book to
} edit the values, we get the identifier as always, and then call the

existingObject(with:) method again to get the Books object and send it to the
EditBookViewController object.
To define the Fetched Results Controller, we created a method called
prepareFetchedController() and execute it as soon as the view is loaded. The
The existingObject(with:) method must also be implemented in the
MyDataSource class to get the object the user wants to delete, as shown next.
NSFetchedResultsController object requires a request with a sort descriptor to
know the order in which the Books objects are going to be offered to the
Listing 15-48: Working with objects identifiers
table. In this example, we create the NSFetchRequest object for the Books

entity and assign to it a sort descriptor that sorts the books by title.
import UIKit
Besides the request, the NSFetchedResultsController initializer requires three import CoreData
more values: a reference to the context, the name of the property that is
class MyDataSource: UITableViewDiffableDataSource<Sections, NSManagedObjectID> {
going to be used to create the sections for the table, and the name of the override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool
file used to cache the information for better performance. The last two {
return true
parameters are not going to be used in this example, so we declared them }
as nil. override func tableView(_ tableView: UITableView, commit editingStyle:
A Fetched Results Controller calls a protocol method to update the UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
snapshot every time a change in the context is detected. The view if let itemID = AppData.dataSourceBooks.itemIdentifier(for: indexPath) {
controller in Listing 15-47 conforms to the NSFetchedResultsControllerDelegate let app = UIApplication.shared
let appDelegate = app.delegate as! AppDelegate
protocol to be able to implement this method. In the method, all we need let context = appDelegate.persistentContainer.viewContext
to do is to cast the generic snapshot to an object with the right data types if let item = try? context.existingObject(with: itemID) as? Books {
and apply it to the diffable data source. This process updates the Table Task(priority: .high) {
View and shows the new values on the screen. await deleteBook(context: context, item: item)
}
Once we have the NSFetchedResultsController object, we must call the }
performFetch() method to fetch the objects from the Persistent Store. }
}

} Search
func deleteBook(context: NSManagedObjectContext, item: Books) async {
await context.perform {

context.delete(item)
do {
try context.save() The process to allow users to search for values in a Table View associated
} catch {
print("Error: \(error)") with an NSFetchedResultsController object does not differ from any other we
} have seen before. We must define a Search Controller, conform to the
}
} UISearchResultsUpdating and UISearchBarDelegate protocols, and implement their
} methods (see Chapter 10, Listing 10-35). In the updateSearchResults(for:)

method, we must modify the predicate of the request assigned to the
NSFetchedResultsController object to search for the value inserted by the user,
The process is the same as before, but now we call the existingObject(with:)
and we also need to implement the searchBarCancelButtonClicked() method to
method to get the Books object to be deleted. Notice that we also define
assign an empty predicate to clear the controller and list all the books
the data type for the UITableViewDiffableDataSource object as NSManagedObjectID.
available again when the Cancel button is pressed. The following is the
BooksViewController class we need to allow the user to search for books by
Do It Yourself: Update the ApplicationData structure with the code in
title.
Listing 15-46, the BooksViewController class with the code in Listing 15-
47, and the MyDataSource class with the code in Listing 15-48. Run the Listing 15-49: Searching for books by title
application. Everything works as before but now the books are 
provided by the Fetched Results Controller. import UIKit
import CoreData

class BooksViewController: UITableViewController, NSFetchedResultsControllerDelegate,


UISearchResultsUpdating, UISearchBarDelegate {
var context: NSManagedObjectContext!
var searchController: UISearchController!

override func viewDidLoad() {


super.viewDidLoad()
let app = UIApplication.shared
let appDelegate = app.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.delegate = self
navigationItem.searchController = searchController

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "booksCell")


prepareDataSource()
prepareFetchedController() override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
} if segue.identifier == "showEditBook" {
override func viewWillAppear(_ animated: Bool) { if let path = tableView.indexPathForSelectedRow {
super.viewWillAppear(animated) if let itemID = AppData.dataSourceBooks.itemIdentifier(for: path) {
do { if let item = try? context.existingObject(with: itemID) as? Books {
try AppData.fetchedController.performFetch() let controller = segue.destination as! EditBookViewController
} catch { controller.selectedBook = item
print("Error") }
} }
} }
func prepareDataSource() { }
AppData.dataSourceBooks = MyDataSource(tableView: tableView) { tableView, indexPath, }
itemID in func updateSearchResults(for searchController: UISearchController) {
let cell = tableView.dequeueReusableCell(withIdentifier: "booksCell", for: indexPath) if let text = searchController.searchBar.text {
if let item = try? self.context.existingObject(with: itemID) as? Books { let search = text.trimmingCharacters(in: .whitespaces)
var config = cell.defaultContentConfiguration() if !search.isEmpty {
config.text = item.title let request = AppData.fetchedController.fetchRequest
config.secondaryText = item.author?.name ?? "Undefined" request.predicate = NSPredicate(format: "title CONTAINS[cd] %@", search)
config.secondaryTextProperties.color = .systemGray try? AppData.fetchedController.performFetch()
}
if let data = item.thumbnail, let image = UIImage(data:data){ }
config.image = image }
} else { func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
config.image = UIImage(named: "nothumbnail") let request = AppData.fetchedController.fetchRequest
} request.predicate = nil
config.imageProperties.maximumSize = CGSize(width: 60, height: 60) try? AppData.fetchedController.performFetch()
cell.contentConfiguration = config }
} @IBAction func editBooks(_ sender: UIBarButtonItem) {
return cell let editing = !tableView.isEditing
} tableView.setEditing(editing, animated: true)
} }
func prepareFetchedController() { }
let request: NSFetchRequest<Books> = Books.fetchRequest() 
let sort = NSSortDescriptor(key: "title", ascending: true, selector:
#selector(NSString.caseInsensitiveCompare(_:)))
request.sortDescriptors = [sort] To perform a search, we do not have to replace the request or create a new
AppData.fetchedController = NSFetchedResultsController(fetchRequest: request, NSFetchedResultsController object, all we need to do is to assign a new
managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
AppData.fetchedController.delegate = self predicate to the current request and call the performFetch() method to
} execute the request again. In Listing 15-49, we modify the predicate every
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
time the text in the search bar changes and assign the original predicate
let newsnapshot = snapshot as NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID> back when the search is cancelled (in this case, there was no original
AppData.dataSourceBooks.apply(newsnapshot, animatingDifferences: true) predicate, so we declare it as nil).
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showEditBook", sender: self)
}

Do It Yourself: Update the BooksViewController class with the code in Sections


Listing 15-49. Run the application. The rows and sections should

appear or disappear according to the term inserted in the bar.
Of course, an NSFetchedResultsController object can work with sections. The
object can take a property of an NSManagedObject object and generate
sections with its values. For example, we can create one section for every
author. The NSFetchedResultsController object splits the information in sections,
with every section containing the books of a particular author, and
automatically assigns the name of the author to the section’s title. All we
need to do is to declare the property we want to use as the value of the
sectionNameKeyPath argument in the object’s initializer, as shown in the
following example.

Listing 15-50: Organizing the Books objects in sections



func prepareFetchedController() {
let request: NSFetchRequest<Books> = Books.fetchRequest()
let sort1 = NSSortDescriptor(key: "author.name", ascending: true, selector:
#selector(NSString.caseInsensitiveCompare(_:)))
let sort2 = NSSortDescriptor(key: "title", ascending: true, selector:
#selector(NSString.caseInsensitiveCompare(_:)))
request.sortDescriptors = [sort1, sort2]

AppData.fetchedController = NSFetchedResultsController(fetchRequest: request,


managedObjectContext: context, sectionNameKeyPath: "author.name", cacheName: nil)
AppData.fetchedController.delegate = self
}

The value of the sectionNameKeyPath argument determines the property we


want to use to separate the objects in sections (author.name in this case), but
we also need to sort the objects to coincide with this configuration. When
working with sections, the first sort descriptor must sort the objects by the
value of the same property we used to generate the sections. In the
example of Listing 15-50, we first sort the sections by the value of the name
property of the author relationship, and then add another NSSortDescriptor
object to sort the objects inside each section by the value of the title When the data source needs the title for a section, it calls the delegate
property. method implemented in Listing 15-51. In this method, we get the list of
To display the sections' titles, the data source must implement the sections from the sections property, read the information of the current
tableView(UITableView, titleForHeaderInSection:) method, as we did in previous section, and use the name property to return its name. Now the table
examples (see Listing 10-38). In this method, we must get the section's title presents sections of books organized by author and ordered by title.
(the author's name) and return it. This information is provided by
NSFetchedResultsController objects through properties defined in a protocol Figure 15-23: Organizing the books in sections
called NSFetchedResultsSectionInfo. The following are the properties available.

numberOfObjects—This property returns the number of objects in


the section.
objects—This property returns an array with references to the
objects in the section.
name—This property returns the name of the section.
indexTitle—This property returns the index’s title for the section (by
default, the index’s title is the capitalized first letter of the name of
the section).

Using these properties, we can get the name of each section from the
Fetched Results Controller and return it. The following is the method we
need to add to the MyDataSource class.

Listing 15-51: Implementing the protocol method to get the sections' titles

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) ->
String? {
if let sections = AppData.fetchedController.sections {
let sectionInfo = sections[section] 
return sectionInfo.name
}
return nil Do It Yourself: Update the prepareFetchedController() method in the
} class with the code in Listing 15-50. Add the
BooksViewController

method in Listing 15-51 to the MyDataSource class. Run the

application. You should see the books ordered by author, as shown To-Many Relationships
in Figure 15-23.

If we want to incorporate an index for the sections, as we did in the The previous examples assumed that there was only one author per book,
examples of Chapter 10, we must implement the sectionIndexTitles() method but sometimes multiple authors collaborate to write a book. To assign
in the data source and return the array we get from the sectionIndexTitles multiple Authors objects to a book, we must turn the author relationship of
property, as in the following example (the property returns an array with the Books entity into a To-Many relationship, as shown below.
the initial letters of the sections’ names capitalized).
Figure 15-24: Many-To-Many relationship
Listing 15-52: Generating an index

override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return AppData.fetchedController.sectionIndexTitles
}

Do It Yourself: Add the method in Listing 15-52 to the MyDataSource 


class and run the application again. You should see the index on the
Now, both relationships are of type To-Many, which means we can assign
right side of the table (see Chapter 10, Figure 10-32).
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 was taking 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 can't do that 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
array into an NSSet object, we must implement the following initializers.

NSSet(set: Set)—This initializer creates an NSSet object with the set


provided by the attribute.
NSSet(array: Array)—This initializer creates an NSSet object with the
var authors: String!
array provided by the attribute. if let list = item.author as? Set<Authors> {
let listNames = list.map({ $0.name ?? "Undefined" })
if !listNames.isEmpty {
In our application, the first place we need to read these values is in the authors = listNames.joined(separator: ", ")
BooksViewController class. Every time a cell is configured, we must cast the }
}
NSSet object returned by the book's author property to a Swift set and create
config.secondaryText = authors ?? "Undefined"
a string with the names of all the authors assigned to the book to show config.secondaryTextProperties.color = .systemGray
them on the screen, as shown next.
if let data = item.thumbnail, let image = UIImage(data:data){
config.image = image
Listing 15-53: Reading the values of a To-Many relationship } else {
config.image = UIImage(named: "nothumbnail")
 }
import UIKit config.imageProperties.maximumSize = CGSize(width: 60, height: 60)
import CoreData cell.contentConfiguration = config
}
class BooksViewController: UITableViewController, NSFetchedResultsControllerDelegate { return cell
var context: NSManagedObjectContext! }
}
override func viewDidLoad() { func prepareFetchedController() {
super.viewDidLoad() let request: NSFetchRequest<Books> = Books.fetchRequest()
let app = UIApplication.shared let sort = NSSortDescriptor(key: "title", ascending: true, selector:
let appDelegate = app.delegate as! AppDelegate #selector(NSString.caseInsensitiveCompare(_:)))
context = appDelegate.persistentContainer.viewContext request.sortDescriptors = [sort]
AppData.fetchedController = NSFetchedResultsController(fetchRequest: request,
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "booksCell") managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
prepareDataSource() AppData.fetchedController.delegate = self
prepareFetchedController() }
} func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
override func viewWillAppear(_ animated: Bool) { didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
super.viewWillAppear(animated) let newsnapshot = snapshot as NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>
do { AppData.dataSourceBooks.apply(newsnapshot, animatingDifferences: true)
try AppData.fetchedController.performFetch() }
} catch { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Error") performSegue(withIdentifier: "showEditBook", sender: self)
} }
} override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
func prepareDataSource() { if segue.identifier == "showEditBook" {
AppData.dataSourceBooks = MyDataSource(tableView: tableView) { tableView, indexPath, if let path = tableView.indexPathForSelectedRow {
itemID in if let itemID = AppData.dataSourceBooks.itemIdentifier(for: path) {
let cell = tableView.dequeueReusableCell(withIdentifier: "booksCell", for: indexPath) if let item = try? context.existingObject(with: itemID) as? Books {
if let item = try? self.context.existingObject(with: itemID) as? Books { let controller = segue.destination as! EditBookViewController
var config = cell.defaultContentConfiguration() controller.selectedBook = item
config.text = item.title }

}
} if selectedBook != nil {
} bookTitle.text = selectedBook.title
} bookYear.text = String(selectedBook.year)
@IBAction func editBooks(_ sender: UIBarButtonItem) {
let editing = !tableView.isEditing var authors: String!
tableView.setEditing(editing, animated: true) if let list = selectedBook.author as? Set<Authors> {
} let listNames = list.map({ $0.name ?? "Undefined" })
} if !listNames.isEmpty {
 authors = listNames.joined(separator: ", ")
}
selectedAuthors = Array(list)
In this example, we cast the value of the author property to a Set<Authors> }
value. This creates a Swift set with Authors objects representing all the authorName.text = authors ?? "Undefined"
}
authors assigned to the book, so we map the values into an array of strings }
and call the joined() method to create a single string with the names @IBAction func saveBook(_ sender: UIBarButtonItem) {
separated by comma. let title = bookTitle.text!.trimmingCharacters(in: .whitespaces)
let year = Int16(bookYear.text!)
Now that the cells can show multiple authors per book, is time to allow the if title != "" && year != nil {
user to assign them. The following are the changes we must introduce to Task(priority: .high) {
await storeBook(title: title, year: year!)
the EditBookViewController class to allow the user to select multiple authors }
per book. }
}
func storeBook(title: String, year: Int16) async {
Listing 15-54: Assigning multiple authors per book await context.perform {
 if self.selectedBook != nil {
self.selectedBook.title = title
import UIKit self.selectedBook.year = year
import CoreData self.selectedBook.author = NSSet(array: self.selectedAuthors)
} else {
class EditBookViewController: UIViewController { let newBook = Books(context: self.context)
@IBOutlet weak var bookTitle: UITextField! newBook.title = title
@IBOutlet weak var bookYear: UITextField! newBook.year = year
@IBOutlet weak var authorName: UILabel! newBook.author = NSSet(array: self.selectedAuthors)
var context: NSManagedObjectContext! }
var selectedAuthors: [Authors] = [] do {
var selectedBook: Books! try self.context.save()
} catch {
override func viewDidLoad() { print("Error: \(error)")
super.viewDidLoad() }
let app = UIApplication.shared }
let appDelegate = app.delegate as! AppDelegate await MainActor.run {
context = appDelegate.persistentContainer.viewContext if let book = self.selectedBook {
var currentSnapshot = AppData.dataSourceBooks.snapshot()
bookTitle.becomeFirstResponder() currentSnapshot.reloadItems([book.objectID])
AppData.dataSourceBooks.apply(currentSnapshot) To show the user the authors currently selected, we implement the prepare()
}
self.closeScene() method and send the value of the selectedAuthors property to the
} AuthorsViewController controller. The controller receives this value, and adds
}
or removes authors from the array according to the selections performed
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "selectAuthor" { by the user on the table, as shown below.
let controller = segue.destination as! AuthorsViewController
controller.selectedAuthors = selectedAuthors
} Listing 15-55: Selecting and deselecting authors
} 
func closeScene() {
navigationController?.popViewController(animated: true) import UIKit
} import CoreData
@IBAction func backAuthor(_ segue: UIStoryboardSegue) {
if segue.identifier == "backFromList" { class AuthorsViewController: UITableViewController {
let controller = segue.source as! AuthorsViewController var context: NSManagedObjectContext!
var selectedAuthors: [Authors]!
selectedAuthors = controller.selectedAuthors
} else if segue.identifier == "backFromNew" {
let controller = segue.source as! EditAuthorViewController override func viewDidLoad() {
super.viewDidLoad()
selectedAuthors.append(controller.selectedAuthor)
} let app = UIApplication.shared
var authors: String! let appDelegate = app.delegate as! AppDelegate
context = appDelegate.persistentContainer.viewContext
let listNames = selectedAuthors.map({ $0.name ?? "Undefined" })
if !listNames.isEmpty {
authors = listNames.joined(separator: ", ") tableView.register(UITableViewCell.self, forCellReuseIdentifier: "authorsCell")
prepareDataSource()
}
authorName.text = authors ?? "Undefined" }
} override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
 Task(priority: .high) {
await loadRequest()

The values are now stored in an array, so we always know which are the await MainActor.run {
prepareSnapshot()
authors selected by the user. If the user is editing a book, we read the }
author property of the selected book and follow the same procedure as }
before to turn the value into a Swift set and show the names of the authors }
func prepareDataSource() {
on the screen. Notice that at the end of the process, we assign the set of AppData.dataSourceAuthors = UITableViewDiffableDataSource<Sections,
Authors objects to the selectedAuthors property to initialize the array NSManagedObjectID>(tableView: tableView) { tableView, indexPath, itemID in
let cell = tableView.dequeueReusableCell(withIdentifier: "authorsCell", for: indexPath)
(selectedAuthors = Array(list)). When the user decides to save the book, we if let item = AppData.listOfAuthors.first(where: { $0.objectID == itemID }) {
perform the inverse procedure. The values in the selectedAuthors array are var config = cell.defaultContentConfiguration()
stored in an NSSet object and assigned to the author property. config.text = item.name ?? "Undefined"
cell.contentConfiguration = config

let selected = self.selectedAuthors.contains(where: { $0.name == item.name }) otherwise, we add it to the array. This makes sure that the array only
cell.accessoryType = selected ? .checkmark : .none
} contains the authors currently selected by the user.
return cell After the user selects or deselects an author, we perform the Unwind
}
}
Segue, get the array back from the EditBookViewController controller, and
func loadRequest() async { update the names on the screen (see the backAuthor() method in Listing 15-
await context.perform { 54). Now the user can select or deselect an author and add as many
let request: NSFetchRequest<Authors> = Authors.fetchRequest()
do { authors to a book as needed.
AppData.listOfAuthors = try self.context.fetch(request)
} catch {
print("Error: \(error)") Do It Yourself: Open the Core Data model. Select the Books entity
} and change the Type of the author relationship to To-Many (Figure
}
} 15-24). Update the BooksViewController class with the code in Listing
func prepareSnapshot() { 15-53, the EditBookViewController class with the code in Listing 15-54,
var snapshot = NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>()
snapshot.appendSections([.main]) and the AuthorsViewController class with the code in Listing 15-55.
snapshot.appendItems(AppData.listOfAuthors.map({ $0.objectID })) Assign the “selectAuthor" identifier to the segue that connects the
AppData.dataSourceAuthors.apply(snapshot, animatingDifferences: false)
} EditBookViewController scene to the AuthorsViewController scene. Run the
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { application. Press the Add Book button. Press the Select button to
if let itemID = AppData.dataSourceAuthors.itemIdentifier(for: indexPath) {
if let item = AppData.listOfAuthors.first(where: { $0.objectID == itemID }) { add an author. You should be able to add as many authors as you
if selectedAuthors.contains(where: { $0.name == item.name }) {
want.
if let index = selectedAuthors.firstIndex(of: item) {
selectedAuthors.remove(at: index)
} These types of relationships also change the way we search for values. For
} else {
selectedAuthors.append(item) instance, we cannot search for a book by author as we did before because
} now a book may be associated to many authors. In this case, we must tell
}
performSegue(withIdentifier: "backFromList", sender: self)
the predicate to search for the value inside the set of authors. For this
} purpose, predicates can include the following keywords.
}
}
 ANY—This keyword returns true when the condition is true for some
of the values in the set.
This is the view controller that displays all available authors. To show which
ALL—This keyword returns true when the condition is true for all the
one has been previously selected, we add a checkmark to the cell when
values in the set.
the author is already in the selectedAuthors array. Then, when a cell is
selected by the user, we check whether the author was previously selected NONE—This keyword returns true when the condition is false for all
or not. If it was selected, we remove it from the selectedAuthors array, the values in the set.
We have introduced predicate keywords earlier in this chapter. They are The example we have been working on so far turns the NSSet object
included in the format string to determine the way the predicate filters the returned by the author relationship into an array of Authors objects and
data. For our example, we can add the ANY keyword in front of the then adds or removes authors from this array, but if we need to add or
comparison to get the books with an author relationship that contains at remove values directly from the NSSet object, we must turn it into an
least one author with a specific name, as shown next. NSMutableSet object. This class creates a mutable set and therefore it allows
us to add or remove values from the object. To create an NSMutableSet
Listing 15-56: Fetching books by author object from an NSSet object, the NSManagedObject class includes the following
 method.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
do {
mutableSetValue(forKey: String)—This method reads the NSSet
try AppData.fetchedController.performFetch() object of the relationship indicated by the forKey attribute and
} catch {
print("Error")
returns an NSMutableSet with the values.
}
let request: NSFetchRequest<Books> = Books.fetchRequest() The NSMutableSet class includes the following methods to add and remove
request.predicate = NSPredicate(format: "ANY author.name == %@", "Stephen King")
if let list = try? context.fetch(request) { items in the set.
for book in list {
print(book.title!)
} add(Any)—This method adds the object specified by the argument
} to the set.
}
 remove(Any)—This method removes the object specified by the
argument from the set.
This example updates the viewWillAppear() method of the BooksViewController
class to include a request that finds all the books associated with an author The following example shows a possible implementation of these methods.
named "Stephen King". The predicate reads all the Authors objects in the We get the object representing the author with the name "Stephen King"
relationship and returns the book when one of the names matches the and then remove that author from every book.
string.
Listing 15-57: Systematically removing authors from books
Do It Yourself: Update the viewWillAppear() method in the 
class with the code in Listing 15-56. Run the
BooksViewController override func viewWillAppear(_ animated: Bool) {
application and insert a few books with the author Stephen King. You super.viewWillAppear(animated)
do {
should see the names of the books associated with that author try AppData.fetchedController.performFetch()
printed on the console. } catch {
print("Error")

}
let request: NSFetchRequest<Authors> = Authors.fetchRequest() CHAPTER 16 - NOTIFICATIONS
request.predicate = NSPredicate(format: "name == %@", "Stephen King")
if let list = try? context.fetch(request), list.count > 0 {
let author = list[0]

if let books = AppData.fetchedController.fetchedObjects {


for book in books {
let authorSet = book.mutableSetValue(forKey: "author")
authorSet.remove(author)
book.author = authorSet
}
}
try? context.save()
}
}

This example updates the viewWillAppear() method of the BooksViewController


class again to modify all the Books objects in the Persistent Store as soon as
the app is launched. First, we perform a request to get the Authors object
with the name "Stephen King". Then, we read the fetchedObjects property of
the Fetched Results Controller to get the list of books fetched by the
controller, and use a for in loop to modify them one by one. In the loop, we
turn the NSSet object returned by the author relationship into an
NSMutableSet object, remove from the set the Authors object fetched before
with the remove() method, and assign the result back to the author
relationship, effectively removing that author from every book.

Do It Yourself: Update the viewWillAppear() method in the


class with the code in Listing 15-57. Run the
BooksViewController
application. The author Stephen King should be removed from every
book.
16.1 Notification Center named argument is the name of the notification we want to read, and
the object argument is a reference to the object that sent the

notification (set to nil if we want to read notifications posted by any
Besides the techniques we have seen so far to transfer data between object).
different parts of the application, such as sending values from one view
controller to another or providing a common model from which every view Notifications are created from the Notification class. The post() method
controller can get the information it needs, we can also report changes automatically creates a Notification object to represent the notification we
across the application with notifications. Foundation includes the want to post, but we can also do it ourselves from the Notification class
NotificationCenter class to create an object that serves as a Notification Center initializer.
for the whole application. We can send notifications (messages) to this
object and then listen to those notifications from anywhere in the code. Notification(name: Name, object: Any?, userInfo: Dictionary)
The class includes the following property to get a reference to the app's —This initializer creates a Notification object with the information
Notification Center. defined by the arguments. The name argument determines the name
of the notification, the object argument is a reference to the object
default—This type property returns the NotificationCenter object that is sending the notification, and the userInfo argument is a
assigned to the application by default. dictionary with the information we want to send with the notification.

The Notification Center is like a bulletin board; we can post a notification The class includes the following properties to read the values of the
from anywhere in the code and then read it from other objects. The notification.
NotificationCenter class defines the following methods to post and read
notifications. name—This property returns the name of the notification.
object—This property returns a reference to the object that posted
post(name: Name, object: Any?, userInfo: Dictionary)—This
the notification.
method posts a notification to the Notification Center. The name
argument determines the name of the notification, the object
userInfo—This property returns the dictionary attached to the
notification.
argument is a reference to the object that sent the notification, and
the userInfo argument is a dictionary with the information we want to
The name of the notification is created from a structure included in the
pass to the observer.
Notification class called Name. The structure provides the following initializer
notifications(named: Name, object: AnyObject?)—This method to define custom names.
returns a Notifications object with an asynchronous sequence that
contains all the notifications posted to the Notification Center. The

Name(String)—This initializer creates a structure that represents the names.append(newName)

notification's name. The argument is a string with the name we want let center = NotificationCenter.default
to assign to the notification. let name = Notification.Name("Update Data")
center.post(name: name, object: nil, userInfo: nil)
}
Notifications are used for multiple purposes. We can post a notification }
var AppData = ApplicationData()
after a long process is over to tell a view controller that it is time to update 
the interface, we can communicate view controllers with each other in a
large interface, or keep a view controller up to date posting notifications The ApplicationData structure created for this example contains only one
from the model. The following example implements a model that posts a property called names with an array of strings, but we have also included a
notification every time a new value is inserted by the user. For didactic method called addNewName() to assign new values to it. After adding the new
purposes, we are going to use a simple interface that includes just enough value to the names array, the method gets a reference to the NotificationCenter
scenes to show the number of values available and insert new ones. object assigned to our app and posts a notification we called "Update
Data" to report the change.
Figure 16-1: Interface to test notifications The Notification Center creates an asynchronous sequence with all the
notifications received (see Asynchronous Sequences in Chapter 14). The
sequence is managed by a Notifications object that we can get from the
notifications() method. To read this sequence, all we need to do is to create
an asynchronous for in loop with this object.

Listing 16-2: Listening to notifications



 import UIKit

class ViewController: UIViewController {


The model must store the value inserted by the user in the Text Field and @IBOutlet weak var counter: UILabel!
then post a notification, so the initial scene can update its interface.
override func viewDidLoad() {
super.viewDidLoad()
Listing 16-1: Sending notifications from the model Task(priority: .background) {
 await readNotifications()
}
import Foundation }
func readNotifications() async {
struct ApplicationData { let center = NotificationCenter.default
var names: [String] = [] let name = Notification.Name("Update Data")

mutating func addNewName(newName: String) { for await _ in center.notifications(named: name, object: nil) {
let current = AppData.names array, and posts an "Update Data" notification. The Notification Center
await MainActor.run {
self.counter.text = String(current.count) stores the notification in the asynchronous sequence, the for in loop in the
} ViewController object detects that a new value is available in the sequence,
}
}
performs a cycle, and the current number of values stored in the names
} array is shown on the screen.

Do It Yourself: Create a new project. Embed the initial scene in a


Because we must wait for the notifications to arrive, we mark the for in loop
Navigation Controller. Add a second scene to the Storyboard. Add
with the await keyword and put it inside an asynchronous method. Every
two labels and a button to the initial scene, and a label, a Text Field,
time a notification is received, the loop performs a cycle, we get the total
number of names stored in the names array and assign it to the label. Notice and a button to the second scene (Figure 16-1). Connect the button
that we call the run() method on the Main Actor to make sure that the in the initial scene to the second scene with a Show segue. Create a
statement that interacts with the label runs in the main thread (see Main Swift file called ApplicationData.swift for the model in Listing 16-1.
Actor in Chapter 14). Create a subclass of the UIViewController class called SecondViewController
The values in the model are added from the second scene. The view and assign it to the second scene. Complete the ViewController class
controller for this scene has to take the value inserted by the user in the with the code in Listing 16-2, and the SecondViewController class with
Text Field and call the addNewName() method to add it to the model. the code in Listing 16-3. Connect the elements to their respective
Outlets and Actions. Run the application, press the button to go to
Listing 16-3: Adding new values to the model the second scene, and insert a few names. Move back to the initial

scene. The interface should show the total number of names
import UIKit
inserted in the form.
class SecondViewController: UIViewController {
@IBOutlet weak var name: UITextField!
The Notification object that represents the notification includes the userInfo
@IBAction func saveName(_ sender: UIButton) { property, which allows us to send information from one object to another.
if let value = name.text { The values that can be included in this dictionary are Property List values
AppData.addNewName(newName: value)
} (NSNumber, NSString, NSDate, NSArray, NSDictionary, NSData, and the equivalents
name.text = "" in Swift). There are multiple applications for this property. Considering our
}
} previous example, we could include the string inserted by the user and
 perform an additional task when the notifications are related to a specific
name, as we do next.
When a name is inserted into the Text Field and the Save button is pressed,
the saveName() method adds the value to the model by calling the model's Listing 16-4: Adding information to the notification
addNewName() method. The model receives the value, stores it in the names 

import Foundation The values from the dictionary are returned as values of type Any, so we
struct ApplicationData { must cast them to the right type. The example in Listing 16-5 reads the
var names: [String] = [] value of the "type" key, cast it as a String, and then compares it with the
string "John". If the values match, we print a message on the console.
mutating func addNewName(newName: String) {
names.append(newName)
Do It Yourself: Update the ApplicationData structure with the code in
let center = NotificationCenter.default
let name = Notification.Name("Update Data") Listing 16-4 and the readNotifications() method in the ViewController class
let info = ["type": newName]
with the code in Listing 16-5. Run the application. A message should
center.post(name: name, object: nil, userInfo: info)
} be printed on the console every time you insert the name John.
}
var AppData = ApplicationData()
 If we do not want to post any more notifications, we can stop the process
in the model (a simple if else statement will suffice), but if we want to stop
The code in Listing 16-4 declares a dictionary with the key "type" and the processing the notifications from a receiver, we must cancel the task. For
value inserted by the user and assigns it to the userInfo argument of the instance, if we have two or more view controllers that are processing the
post() method. Now, we can check this value from our ViewController class. notifications but we want only one of them to stop doing it, we can store
the task in a constant and called the cancel() method, as we did in Chapter
Listing 16-5: Reading the value in the notification 14 (see Listing 14-3).
 The following example cancels the task in our ViewController class after 10
func readNotifications() async { seconds, so the label is no longer updated with any of the values inserted
let center = NotificationCenter.default
by the user after the time expires.
let name = Notification.Name("Update Data")

for await notification in center.notifications(named: name, object: nil) { Listing 16-6: Cancelling the task
if let info = notification.userInfo {
let type = info["type"] as? String 
if type == "John" { override func viewDidLoad() {
print("Our name was inserted") super.viewDidLoad()
} let myTask = Task(priority: .background) {
} await readNotifications()
let current = AppData.names }
await MainActor.run { Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) { (timer) in
self.counter.text = String(current.count) myTask.cancel()
} }
} }
} 

System Notifications }


Do It Yourself: Create a new project. Add a Text View to the initial
Besides the notifications posted by our app, the system also posts scene. Update the ViewController class with the code in Listing 16-7.
notifications to the Notification Center all the time to report changes in the Run the application and type some text inside the Text View. You
interface or in other objects running the application. There are hundreds of should see a message on the console every time a character is added
these notifications available, including many defined in some of the classes or removed.
we already studied. For example, the UITextView class includes notifications
like textDidChangeNotification to report that the content of the Text View Another useful notification is called didChangeNotification, defined in a
changed. The following is a simple ViewController class we can use to test this structure called UIContentSizeCategory. This notification is posted every time
notification (the example assumes that we have an interface with a Text the user changes the size of the Dynamic Font types from the Settings app.
View). Every time the user changes the text, the Text View posts the We introduced Dynamic Font types in Chapter 5. They can be created from
textDidChangeNotification notification and the view controller responds by code with the preferredFont() method of the UIFont class (see Listing 5-14) or
printing a message on the console. selected from the Attributes Inspector panel. There are different types
available, such as Body, Headline, and more. When we select one of these
Listing 16-7: Responding to the Text View notification types, the system sets the size of the font to match the size selected by the
 user from the Settings app (Accessibility/Display & Text Size/Large Text).
import UIKit The problem is that the views that were already loaded are not
class ViewController: UIViewController { automatically updated. To make sure that all the important text in our
@IBOutlet weak var mainText: UITextView! interface is updated to the current size selected by the user, we must listen
override func viewDidLoad() {
to the didChangeNotification notification and perform the update ourselves.
super.viewDidLoad()
Task(priority: .background) {
Listing 16-8: Responding to font size changes in the Settings app
await receiveNotifications()
} 
} import UIKit
func receiveNotifications() async {
let center = NotificationCenter.default class ViewController: UIViewController {
let name = UITextView.textDidChangeNotification @IBOutlet weak var mainText: UITextView!

for await notification in center.notifications(named: name, object: nil) { override func viewDidLoad() {
if notification.name == name { super.viewDidLoad()
print("The Text View was modified") mainText.font = UIFont.preferredFont(forTextStyle: .body)
}
} Task(priority: .background) {
} await receiveNotifications()

} the accelerometer. Every time we want to know the orientation of the


}
func receiveNotifications() async { device, we must call the beginGeneratingDeviceOrientationNotifications() method
let center = NotificationCenter.default first to make sure that we are getting accurate information, and when our
let name = UIContentSizeCategory.didChangeNotification
for await notification in center.notifications(named: name, object: nil) { app does not require updates anymore, we must call the
if notification.name == name {
endGeneratingDeviceOrientationNotifications() method to tell the system that it can
await MainActor.run {
self.mainText.font = UIFont.preferredFont(forTextStyle: .body) turn off the accelerometer. In the following example, we apply these
}
}
methods and print the current orientation on the console.
}
} Listing 16-9: Detecting the current orientation
}
 
import UIKit
When the system detects a change in the size of the fonts performed from
class ViewController: UIViewController {
the Settings app, it posts the didChangeNotification notification to tell the var device = UIDevice.current
applications that the content size is different. In the example of Listing 16-
override func viewWillAppear(_ animated: Bool) {
8, we initialize the Text View with a font of type body and then reassign this device.beginGeneratingDeviceOrientationNotifications()
font type every time a notification is received. This forces the system to
Task(priority: .background) {
update the interface and show the text inside the Text View in the size
await receiveNotifications()
selected by the user. }
}
override func viewDidDisappear(_ animated: Bool) {
Do It Yourself: Update the ViewController class with the code in device.endGeneratingDeviceOrientationNotifications()
Listing 16-8. Run the application, click the Home button to go to the }
func receiveNotifications() async {
home screen, and change the size of the font from the Settings app let center = NotificationCenter.default
(Accessibility/Display & Text Size/Large Text). Click the Home button let name = UIDevice.orientationDidChangeNotification
for await notification in center.notifications(named: name, object: nil) {
again and open the app. You should see the text in the new size if notification.name == name {
let orientation = device.orientation
selected from Settings.
switch orientation {
case .portrait, .portraitUpsideDown:
print("Portrait")
There is at least one more notification worth mentioning called
case .landscapeLeft, .landscapeRight:
orientationDidChangeNotification, defined in the UIDevice class. This notification is print("Landscape")
default:
posted to the Notification Center by the system when the orientation of
print("Undefined")
the device changes, but only if the accelerometer is enabled. As we }
}
mentioned in Chapter 5, the UIDevice class offers two methods to activate }
} 16.2 User Notifications
}
 

Because we want to detect every rotation while the view is visible, we A different type of notification is the User Notification. These are
enable the accelerometer in the viewWillAppear() method. Every time the notifications that the system shows to the user when the app has an event
system detects a rotation, it posts a notification. When a notification is to report, such as the completion of a task or real-life events that the user
received, we get the current orientation from the orientation property of the wants to be reminded of. There are three different types of User
UIDevice object and print a message on the console. Notifications: alert, badge, and sound. A badge-type notification displays a
Notice that we also called the endGeneratingDeviceOrientationNotifications() badge with a number over the app's icon, a sound-type notification plays a
method in the viewDidDisappear() method. This is not necessary in our sound, and an alert-type notification may be displayed as a banner, an
application because we only have one scene, but in a more complex Alert View, or a message on the lock screen, depending on the current
interface it is good practice to tell the system that we no longer require state of the device and the configuration set by the user. They can be
updates on the state of the device. The system stops posting notifications scheduled all at once or independently. For instance, we can schedule a
notification that displays an alert and plays a sound, another that displays
and powers down the accelerometer if no other part of the application is
an alert and shows a badge, or another that just plays a sound.
using it.
IMPORTANT: User Notifications are divided into Local Notifications
Do It Yourself: To try this last example, remove the Text View from
and Remote Notifications (also known as Push Notifications). Local
the scene and update the ViewController class with the code in Listing
Notifications are notifications generated by the application running
16-9. Run the application and rotate the device. You should see the
on the device, while Remote Notifications are generated by remote
orientation printed on the console every time it changes.
servers and received by the system through the network. In this
chapter, we are going to study Local Notifications. For more
information on Remote Notifications, visit our website and follow
the links for this chapter.

User Notifications Framework


For the notifications to be sent, they must be added to the User

Notification Center. The UNUserNotificationCenter class includes the following
User Notifications are created and managed by classes of the User methods to add and remove them.
Notifications framework. The framework includes multiple classes to
control every step of the process, of which the most important is the add(UNNotificationRequest)—This asynchronous method
UNUserNotificationCenter class. This class creates a Notification Center to schedules a new notification in the User Notification Center. The
schedule and manage user notifications. This is like the Notification Center argument is the request for the notification.
studied before but specific for User Notifications. The system creates a removePendingNotificationRequests(withIdentifiers:
UNUserNotificationCenter object to serve as the Notification Center for the
[String])—This method removes the pending notifications with the
application that we can retrieve with the following type method. identifiers specified by the argument.

current()—This type method returns a reference to the The framework includes the UNMutableNotificationContent class to store the
UNUserNotificationCenter object assigned to the app. content of a notification. The following are the properties included in this
class to set the notification's values.
From the UNUserNotificationCenter object, we can manage the notifications.
The first step is to request authorization from the user. The class includes title—This property sets or returns the notification's title.
the following methods for this purpose.
subtitle—This property sets or returns the notification's subtitle.
requestAuthorization(options: UNAuthorizationOptions)— body—This property sets or returns the notification's message.
This asynchronous method requests authorization from the user to badge—This property sets or returns a number to show over the
show notifications and returns a Boolean value to report the result. app's icon.
The options argument is a set of properties that determine the type sound—This property sets or returns the sound we want to play
of notifications we want to show. The properties available are badge, when the notification is delivered to the user. It is an object of type
sound, alert, carPlay, criticalAlert, provisional, and announcement. UNNotificationSound.
notificationSettings()—This asynchronous method returns a userInfo—This property sets or returns a dictionary with the
UNNotificationSettingsobject with the current settings. The most useful information we want to send with the notification.
property is authorizationStatus, which returns an enumeration value with
the authorization status (the user may change the status of the These properties define the information the notification is going to show to
authorization anytime from the Settings app). The possible values are the user. Some of these properties store strings, except for the badge
notDetermined, denied, authorized, provisional, and ephemeral. property which takes an NSNumber object, and the sound property which
takes an object of the UNNotificationSound class. This class includes the available for Local Notifications: Time Interval, (the notification is delivered
following initializer and property to get the object. after a certain period of time), Calendar (the notification is delivered on a
specific date), and Location (the notification is delivered in a specific
UNNotificationSound(named: UNNotificationSoundName)— location). The framework defines three classes to create these triggers:
This initializer creates a UNNotificationSound object with the sound UNTimeIntervalNotificationTrigger, UNCalendarNotificationTrigger, and

specified by the named argument. UNLocationNotificationTrigger.

default—This type property returns a UNNotificationSound object with


UNTimeIntervalNotificationTrigger(timeInterval:
the sound defined by the system.
TimeInterval, repeats: Bool)—This initializer creates a Time
Interval trigger that will deliver the notification after the period of
The names of the sounds are defined by a structure of type
UNNotificationSoundName. The structure includes the following initializer.
time determined by the timeInterval argument (in seconds). The
repeats argument determines if the notification will be delivered once
UNNotificationSoundName(rawValue: String)—This initializer or infinite times.
creates a UNNotificationSoundName object with the name of the file that UNCalendarNotificationTrigger(dateMatching:
contains the sound we want to play with the notification. DateComponents, repeats: Bool)—This initializer creates a
Calendar trigger that delivers the notification at the date determined
The UNMutableNotificationContent class also allows us to set the level of by the dateMatching argument. The repeats argument determines if
interruption. By default, notifications are active, which means they are going the notification will be delivered once or infinite times.
to turn on the screen and play sound, but they can also be set to passive
UNLocationNotificationTrigger(region: CLCircularRegion,
(they do not turn on the screen), timeSensitive (they are displayed
immediately, but considering user settings), and critical (they bypass user
repeats: Bool)—This initializer creates a Location trigger that
settings). The class includes the following property to define the delivers the notification when the device is inside a region in the real
interruption level. world determined by the region argument. The repeats argument
determines if the notification will be delivered once or infinite times.
interruptionLevel—This property sets or returns a value that
determines the importance and delivery timing of the notification. It To deliver a notification, we must create a request that contains the
is a UNNotificationInterruptionLevel enumeration with the values active notification, an identifier, and a trigger. The framework defines the
UNNotificationRequest class for this purpose.
(default), critical, passive, and timeSensitive.

User Notifications are posted to the User Notification Center and then
UNNotificationRequest(identifier: String, content:
presented by the system when a certain condition is met. These conditions UNNotificationContent, trigger: UNNotificationTrigger?)—This
are established by objects called Triggers. There are three types of triggers initializer creates a request to deliver the notification specified by the

content argument and at the time or place specified by the trigger This example assumes that we have a button on the interface to send
argument. The identifier argument is a string that we can use later to notifications. We first get a reference to the UNUserNotificationCenter object
manage the request. assigned to the app, and then start a task to ask for authorization to show
alert banners and sound. The requestAuthorization() method is asynchronous,
As we already mentioned, before sending user notifications we must ask so we wait for the user to respond and then enabled or disable the button
the user for permission. Apple recommends doing it only when we really according to the value returned by the method (true if the user authorizes
need it. For instance, if our application contains a scene with a switch for the app or false otherwise).
the user to activate notifications, we should ask permission in this scene When the requestAuthorization() method is called, it creates an Alert View with
and not right after the app is launched. The following example illustrates a message and two buttons to let the user decide, as shown below.
how to do it.
Figure 16-2: Authorization to deliver notifications
Listing 16-10: Asking permission to send notifications

import UIKit
import UserNotifications

class ViewController: UIViewController {


var center: UNUserNotificationCenter!
@IBOutlet weak var sendButton: UIButton!

override func viewDidLoad() {


center = UNUserNotificationCenter.current()
sendButton.isEnabled = false

Task(priority: .high) {
do {
let authorized = try await center.requestAuthorization(options: [.alert, .sound])
await MainActor.run {
self.sendButton.isEnabled = authorized
}
} catch {
print("Error: \(error)")
}
}
}
}


If the user doesn't allow the app to show notifications, it can be done later let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false)

from the Settings app, so the app should always test whether authorization let id = "reminder-\(UUID())"
was granted or not and warn the user in case an action is required. For this let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
purpose, the UNUserNotificationCenter class includes the notificationSettings() do {
method. We should always consult this method before sending try await center.add(request)
notifications to make sure that the app is still authorized to do it. For await MainActor.run {
self.sendButton.isEnabled = false
instance, we can add an Action for the button to call a method that checks }
the status and sends a notification every time it is pressed. } catch {
print("Error: \(error)")
}
Listing 16-11: Checking authorization status }
 

@IBAction func checkAuthorization(_ sender: UIButton) {


Task(priority: .background) { The process to schedule a notification is simple. We must create an
let authorization = await center.notificationSettings() instance of the UNMutableNotificationContent class with the values we want the
if authorization.authorizationStatus == .authorized {
await self.sendNotification() notification to show to the user, create a trigger (in this case we use a Time
} Interval trigger), create an instance of the UNNotificationRequest class with
}
these values to request the delivery of the notification, and finally add the
}
 request to the User Notification Center with the add() method. Notice that
the request identifier must be unique. In our example, we define this
The notificationSettings() method is asynchronous, so we wait for the method unique identifier with a string that includes the word "reminder" followed
to return a value and then check the authorization status. If the value of by a random value generated by the UUID() function.
the authorizationStatus property is equal to authorized, it means that we are
authorized to send notifications and can proceed. The add() method used to Figure 16-3: Notification
send a notification is also asynchronous, so after the authorization is
confirmed, we call our own asynchronous method to send the notification.

Listing 16-12: Scheduling a notification




func sendNotification() async {
let content = UNMutableNotificationContent() Do It Yourself: Create a new project. Add a button to the scene and
content.title = "Reminder"
connect it to the ViewController class with an Outlet called sendButton
content.body = "This is the body of the message"
content.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: and an Action called checkAuthorization(). Complete the ViewController
"alarm.mp3"))
class with the code in Listing 16-10 and the methods in Listings 16-11

and 16-12. Download the alarm.mp3 file from our website and add it Media Attachments
to your project (you can drag and drop the file from Finder). The first

time you run the application the system will ask you to allow the app
to deliver notifications. Press the Allow button. The button on the Besides text and sounds, notifications can also include other types of
interface should be enabled. Press this button to send the media, such as images and videos. The UNMutableNotificationContent class
notification. Press the Home button to close the app. You should see includes the following property to attach additional media to the
the notification popping up on the screen after 30 seconds, as shown notification.
in Figure 16-3.
attachments—This property sets or returns an array of
UNNotificationAttachment objects with the media files we want to show
with the notification.

The attachments are loaded by an object of the UNNotificationAttachment class.


The class includes the following initializer.

UNNotificationAttachment(identifier: String, url: URL,


options: Dictionary?)—This initializer creates an attachment with
the media loaded from the URL specified by the url argument. The
identifier argument is the attachment's unique identifier. And the
options argument is a dictionary with predefined values to configure
the media. The most useful are
UNNotificationAttachmentOptionsThumbnailClippingRectKey to use only a
portion of an image, and UNNotificationAttachmentOptionsThumbnailTimeKey
to select a frame from a video.

To attach an image or a video to the notification, we must create the


UNNotificationAttachment object and assign it to the content's attachments
property. The object takes a unique identifier, that we can create as we
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 }
return nil
from the Assets Catalog, store it in a file, and assign it to the notification. }

Listing 16-13: Attaching an image to a notification
 Because we need to load, process, and save the image in a file, we moved
func sendNotification() async { the code to a new method called getThumbnail(). In this method, we get the
let content = UNMutableNotificationContent() URL of the Documents directory, append the name of the file, and then
content.title = "Reminder"
content.body = "This is the body of the message"
load the image from the Assets Catalog, convert it to data, and store it. The
file's URL is returned by the method and used by the code to attach the
let idImage = "attach-\(UUID())" image to the notification. The result is shown below. The image is displayed
if let urlImage = await getThumbnail(id: idImage) {
if let attachment = try? UNNotificationAttachment(identifier: idImage, url: urlImage, inside the banner and expanded when the user drags the banner down.
options: nil) {
content.attachments = [attachment]
} Figure 16-4: Attachment
}
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)

let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
try await center.add(request)
await MainActor.run {
self.sendButton.isEnabled = false
}
} catch {
print("Error: \(error)")
}
}
func getThumbnail(id: String) async -> URL? { 
let manager = FileManager.default
if let docURL = manager.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = docURL.appendingPathComponent("\(id).png") Do It Yourself: Update the sendNotification() method in the
if let image = UIImage(named: "husky") {
ViewControllerclass with the code in Listing 16-13 and add the
if let thumbnail = await image.byPreparingThumbnail(ofSize: CGSize(width: 100, height:
100)) { getThumbnail(id:)method below. Download the husky.png image from
if let imageData = thumbnail.pngData() {
if let _ = try? imageData.write(to: fileURL) {
our website and add it to the Assets Catalog. Run the application.
return fileURL Press the button to post a notification, and press the Home button to
}
} close the app. You should see a notification with the picture of the
} husky, as in Figure 16-4.
}

Provisional Notifications

Asking the user for permission can be a bit disruptive for some
applications. If we consider that due to the characteristics of our app the
user's acceptance to receive notifications may be implicit, we can post
provisional notifications. These are quiet notifications that only show in the
Notification Center (they are not displayed in the Locked or Home screens)
and include buttons for the user to decide whether to keep them or turn
them off. To get our app to post provisional notifications, all we need to do
is to add the provisional option to the requestAuthorization() method, as shown
next.

Listing 16-14: Scheduling provisional notifications



override func viewDidLoad() {
center = UNUserNotificationCenter.current()
sendButton.isEnabled = false

Task(priority: .background) {
do {
let authorized = try await center.requestAuthorization(options: [.alert, .sound, .provisional])
await MainActor.run {
self.sendButton.isEnabled = authorized
}
} catch {
print("Error: \(error)")
}
}
}

If we use provisional notifications, the user is not prompted for


authorization, the app is automatically authorized to post notifications, but
the status is set as provisional instead of authorized, so we must consider this
condition when we check the status.
Listing 16-15: Checking the status of a provisional authorization Notifications Delegate


@IBAction func checkAuthorization(_ sender: UIButton) {
Task(priority: .background) {
let authorization = await center.notificationSettings() If the user is working with the app when a notification is delivered, the
let status = authorization.authorizationStatus
if status == .authorized || status == .provisional {
notification is not displayed on the screen, but we can assign a delegate to
await self.sendNotification() the User Notification Center to change this behavior. For this purpose, the
} framework defines the UNUserNotificationCenterDelegate protocol, which allows
}
} us to do two things: we can decide whether to show the notification when
 the app is running and respond to actions performed by the user.

Because provisional notifications are only shown on the Notification userNotificationCenter(UNUserNotificationCenter,


Center, the user must open the Notification Center to see them, and then
willPresent: UNNotification, withCompletionHandler: Block)
press the Manage button to decide whether to keep them or turn them off.
—This method is called by the User Notification Center on the
delegate when the application is active and a notification has to be
Figure 16-5: Provisional notifications in the Notification Center
delivered. The withCompletionHandler argument is a closure that we
must execute to tell the system what to do with the notification. The
closure receives an argument of type UNNotificationPresentationOptions to
define what type of notification to show. The possible values are badge,
banner, list, and sound.

userNotificationCenter(UNUserNotificationCenter,
 didReceive: UNNotificationResponse,
withCompletionHandler: Block)—This method is called by the
Do It Yourself: Update the viewDidLoad() method with the code in User Notification Center when the user interacts with the notification
Listing 16-14 and the checkAuthorization() method with the code in
(performs an action). The didReceive argument is an object with
Listing 16-15. Uninstall the app and run it again from Xcode. Post a
information about the notification and the action performed, and the
notification. Go to the Home screen and drag your finger from the withCompletionHandler argument is a closure we must execute after
top to open the Notification Center. You should see the provisional the response is processed.
notification, as shown in Figure 16-5. When a notification is triggered and the app is being executed, the User
Notification Center calls the userNotificationCenter(UNUserNotificationCenter,
willPresent:, withCompletionHandler:) method on its delegate to ask the
application what to do. In this method, we can perform any task we want

and then execute the closure received by the method to specify what we await self.sendNotification()
}
want the system to do with the notification. If we do not want to show the }
notification, we must execute the closure with no parameters, otherwise }
func sendNotification() async {
we must provide a value that determines the type of notification we want
let content = UNMutableNotificationContent()
to show. content.title = "Reminder"
As always, all we need to do is to declare our view controller as the content.body = "This is the body of the message"

delegate and implement the protocol methods. In the following example, let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
we implement the userNotificationCenter(UNUserNotificationCenter, notification:,
let id = "reminder-\(UUID())"
completionHandler:) method to show the notification while the app is running.
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
Listing 16-16: Showing notifications while the app is running try await center.add(request)
await MainActor.run {
 self.sendButton.isEnabled = false
import UIKit }
import UserNotifications } catch {
print("Error: \(error)")
class ViewController: UIViewController, UNUserNotificationCenterDelegate { }
var center: UNUserNotificationCenter! }
@IBOutlet weak var sendButton: UIButton! func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification:
UNNotification, withCompletionHandler completionHandler: @escaping
override func viewDidLoad() { (UNNotificationPresentationOptions) -> Void) {
center = UNUserNotificationCenter.current() completionHandler([.banner])
center.delegate = self }
}
sendButton.isEnabled = false 

Task(priority: .background) {
do {
Do It Yourself: Update the ViewController class with the code in
let authorized = try await center.requestAuthorization(options: [.alert]) Listing 16-16. For this example, we have removed the provisional
await MainActor.run {
self.sendButton.isEnabled = authorized
parameter added to the configuration in Listing 16-14, so the
} notifications are not provisional anymore. Remove the application
} catch {
print("Error: \(error)")
from the device and run it again. Press the button to post a
} notification. Wait for 10 seconds. You should see the notification on
}
} the screen while the app is running.
@IBAction func checkAuthorization(_ sender: UIButton) {
Task(priority: .background) {
let authorization = await center.notificationSettings()
let status = authorization.authorizationStatus
if status == .authorized {
Groups self.sendButton.isEnabled = authorized
}
 } catch {
print("Error: \(error)")
}
The system automatically groups notifications together by app. For }
}
instance, if our application sends multiple notifications to the Notification @IBAction func checkAuthorization(_ sender: UIButton) {
Center, they will all be grouped together and only the last one will be Task(priority: .background) {
let authorization = await center.notificationSettings()
shown to the user. This is the automatic behavior, but we can separate if authorization.authorizationStatus == .authorized {
them in custom groups using identifiers. The UNMutableNotificationContent class await self.sendNotification()
includes the following property for this purpose. }
}
}
threadIdentifier—This property sets or returns a string used to func sendNotification() async {
for group in listGroups {
identify each group of notifications. for message in listMessages {
let content = UNMutableNotificationContent()
content.title = "Reminder \(group)"
All the notifications with the same identifier will be grouped together. The content.body = message
following example separates notifications in two groups called Group One content.threadIdentifier = group
and Group Two. let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)

Listing 16-17: Organizing notifications into groups let id = "reminder-\(UUID())"


let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
 do {
import UIKit try await center.add(request)
import UserNotifications } catch {
print("Error: \(error)")
class ViewController: UIViewController { }
var center: UNUserNotificationCenter! }
@IBOutlet weak var sendButton: UIButton! }
await MainActor.run {
let listGroups = ["Group One", "Group Two"] self.sendButton.isEnabled = false
let listMessages = ["This is message 1", "This is message 2", "This is message 3"] }
}
override func viewDidLoad() { }
center = UNUserNotificationCenter.current() 
sendButton.isEnabled = false
For didactic purposes, we define two arrays with the name of the groups
Task(priority: .background) {
do { ("Group One" and "Group Two") and the messages for the notifications. In
let authorized = try await center.requestAuthorization(options: [.alert]) the sendNotification() method, we iterate through the arrays to create all the
await MainActor.run {
notifications (three per group) and tell the system to which group each

notification belongs by assigning the name of the group to the Summary


threadIdentifier property. In total, we post six notifications in two groups.

Figure 16-6: Notifications in two groups
Users can create notification summaries to get a single alert at a specific
time in the day with all the notifications grouped together. A summary can
include notifications from one or multiple apps. The tool to create a
summary is available in the Notifications option of the Settings app.

Figure 16-7: Summary option in Settings app



Do It Yourself: Update the ViewController class with the code in
Listing 16-17. Run the application. Press the button to post the To configure the summary, we must create a category and add that
notifications. Go to the Lock screen. After 10 seconds you should see category to the User Notification Center. Categories are objects that define
all the notifications organized in two groups (Figure 16-6). actions and behavior associated to a notification or a group of
notifications. The framework offers the UNNotificationCategory class to create
these objects. The following is the initializer used to configure a summary.

UNNotificationCategory(identifier: String, actions:


[UNNotificationAction], intentIdentifiers: [String],
hiddenPreviewsBodyPlaceholder: String?,
categorySummaryFormat: String?, options:
UNNotificationCategoryOptions)—This initializer creates a
category to configure a summary. The identifier argument is a string
that identifies the category, the actions argument defines the actions
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
instead of the notifications when the previews are disabled, the Listing 16-18: Configuring a summary of notifications
categorySummaryFormat argument is the string that describes the 

summary, and the options argument is an array of properties that func sendNotification() async {
let groupID = "Group One"
determine how the notifications associated to the category are going let summaryFormat = "\(listMessages.count) messages"
to be handled. The properties available are customDismissAction
let category = UNNotificationCategory(identifier: groupID, actions: [], intentIdentifiers: [],
(processes the dismiss action) and allowInCarPlay (allows car play to hiddenPreviewsBodyPlaceholder: nil, categorySummaryFormat: summaryFormat, options: [])
center.setNotificationCategories([category])
show notifications).
for message in listMessages {
The UNUserNotificationCenter class includes the following method to register a let content = UNMutableNotificationContent()
content.title = "Reminder"
category in the User Notification Center. content.body = message
content.threadIdentifier = groupID

setNotificationCategories(Set)—This method configures the User let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)
Notification Center to work with the types of notifications and actions
let id = "reminder-\(UUID())"
we want to support. The argument is the set of categories we want to let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
associate to the center. do {
try await center.add(request)
} catch {
When the notifications appeared in a summary, they are sorted according print("Error: \(error)")
}
to their relevance. The system determines this relevance for us, but we can }
suggest a specific order by setting the notification's relevance ourselves. await MainActor.run {
self.sendButton.isEnabled = false
The UNMutableNotificationContent class includes the following property for this }
purpose. }

relevanceScore—This property sets or returns a value of type Double


The notifications are created with the messages in the listMessages array
between 0 and 1 to tell the system how to sort the app's notifications.
introduced in the previous example. To include the notifications in the
The notification with the highest relevance gets featured in the
summary, we define a single group with the identifier "Group One" and
notification summary. create a category with a summary message that includes the number of
notifications included in the summary. The rest of the process is the same
The following are the modifications we need to introduce to the as before.
sendNotification() method from the previous example to include our
If we just run the application, the notifications are displayed as before, but
notifications in a summary. we can go to the Settings app and set a summary, as shown in Figure 16-7.

Below is how the summary created by our app looks like when it is shown Actions
to the user.

Figure 16-8: Notifications summary in the Lock screen
Notifications can show custom actions in the form of buttons and input
fields that the user can interact with to provide feedback without having to
open our app. The actions are defined by two classes: UNNotificationAction
and UNTextInputNotificationAction.

UNNotificationAction(identifier: String, title: String, options:


UNNotificationActionOptions)—This initializer creates an action
represented by a custom button. The identifier argument is a string
that we can use to identify the action, the title argument is the text
shown on the button, and the options argument is a set of properties
that determine how the action should be performed. The properties
available are authenticationRequired (the user is required to unlock the
device), destructive (the button is highlighted), and foreground (the app is
 opened to perform the action).
UNTextInputNotificationAction(identifier: String, title: String,
Do It Yourself: Update the sendNotification() method with the code in options: UNNotificationActionOptions, textInputButtonTitle:
Listing 16-18. Press the Home button, open the Settings app, and set String, textInputPlaceholder: String)—This initializer creates an
a summary for the application, as shown in Figure 16-7. Run the action represented by a custom button that when pressed prompts
application and press the button to post the notifications. You should the system to display an input field. In addition to the arguments
see a summary in the Lock screen with all the notifications at the included by a normal action, these types of actions also include the
time specified in Settings. textInputButtonTitle and textInputPlaceholder arguments to define
the button and the placeholder for the input field.

After the actions we want to include in the notification are defined, we


must create a category to group them together. The UNNotificationCategory
class includes the following initializer to add actions to a notification.
UNNotificationCategory(identifier: String, actions: Listing 16-19: Adding and processing actions for notifications
[UNNotificationAction], intentIdentifiers: [String], options: 

UNNotificationCategoryOptions)—This initializer creates a import UIKit


import UserNotifications
category with the actions specified by the actions argument. The
identifier argument is a string that identifies the category, the class ViewController: UIViewController, UNUserNotificationCenterDelegate {
var center: UNUserNotificationCenter!
intentIdentifiers argument is an array of strings used to guide Siri to @IBOutlet weak var sendButton: UIButton!
produce a better response, and the options argument is an array of
override func viewDidLoad() {
properties that determine how the notifications associated to the center = UNUserNotificationCenter.current()
center.delegate = self
category are going to be handled. The properties available are
customDismissAction (processes the dismiss action) and allowInCarPlay sendButton.isEnabled = false
(allows car play to show notifications). Task(priority: .background) {
do {
When an action is performed, the User Notification Center calls the let authorized = try await center.requestAuthorization(options: [.alert])
await MainActor.run {
userNotificationCenter(UNUserNotificationCenter, didReceive:, withCompletionHandler:) self.sendButton.isEnabled = authorized
method on its delegate. The method receives a UNNotificationResponse object }
} catch {
with information about the action and the notification. The class includes print("Error: \(error)")
the following properties to read the values. }
}
}
actionIdentifier—This property sets or returns a string with the @IBAction func checkAuthorization(_ sender: UIButton) {
Task(priority: .background) {
action's identifier. let authorization = await center.notificationSettings()
notification—This property sets or returns a UNNotification object let status = authorization.authorizationStatus
if status == .authorized {
representing the notification. The object includes the date property to await self.sendNotification()
}
get the date the notification was delivered and the request property
}
with a reference to the UNNotificationRequest object used to schedule the }
func sendNotification() async {
notification, which in turn offers the content property to access the let groupID = "listActions"
values of the notification. let actionDelete = UNNotificationAction(identifier: "deleteButton", title: "Delete", options:
.destructive)
let category = UNNotificationCategory(identifier: groupID, actions: [actionDelete],
The following example adds an action to the notification scheduled in intentIdentifiers: [], options: [])
previous examples and implements the delegate method to process the center.setNotificationCategories([category])

response. let content = UNMutableNotificationContent()


content.title = "Reminder"
content.body = "This is the body of the message"

content.categoryIdentifier = groupID

let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)

let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
try await center.add(request)
await MainActor.run {
self.sendButton.isEnabled = false
}
} catch {
print("Error: \(error)")
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response:
UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let identifier = response.actionIdentifier 
if identifier == "deleteButton" {
print("Delete Message")
} In the example of Listing 16-19, we implement a simple action that shows a
completionHandler()
}
button when the notification is expanded, but we can also include an
} action that shows an input field, so the user can provide feedback right
 from the screen where the notification is being displayed. The following
are the changes we need to introduce to add an action of this type.
This view controller creates a destructive action with the title Delete, and
includes it in a category called “listActions”. Categories have to be added to Listing 16-20: Processing an input action
the User Notification Center first with the setNotificationCategories() method 
and then assigned to the notification's categoryIdentifier property to configure
func sendNotification() async {
the notification, as we did in Listing 16-18. let groupID = "listActions"
Actions are displayed when the user drags down the notification. In this let actionDelete = UNNotificationAction(identifier: "deleteButton", title: "Delete", options:
.destructive)
case, the notification shows the Delete button. If the user presses this let actionInput = UNTextInputNotificationAction(identifier: "inputField", title: "Message",
button, the User Notification Center calls the delegate method to give our options: [])
application the chance to perform a task. In our example, we read the let category = UNNotificationCategory(identifier: groupID, actions: [actionDelete,
actionIdentifier property, compare it with the string "deleteButton" to confirm actionInput], intentIdentifiers: [], options: [])
that the user pressed the Delete button, and then print a message on the center.setNotificationCategories([category])

console in case of success. let content = UNMutableNotificationContent()


content.title = "Reminder"
content.body = "This is the body of the message"
Figure 16-9: Actions in a notification content.categoryIdentifier = groupID
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 10, repeats: false)

let id = "reminder-\(UUID())"
let request = UNNotificationRequest(identifier: id, content: content, trigger: trigger)
do {
try await center.add(request)
await MainActor.run {
self.sendButton.isEnabled = false
}
} catch {
print("Error: \(error)")
}
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response:
UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

let identifier = response.actionIdentifier
if identifier == "deleteButton" { Do It Yourself: Update your ViewController class with the code in
print("Delete Message")
} else if identifier == "inputField" { Listing 16-20 and run the application. Press the button to schedule a
print("Send: \((response as! UNTextInputNotificationResponse).userText)")
notification. Go to the Home screen and wait until the notification is
}
completionHandler() displayed. Expand the notification to see the actions. Click the
}

Message button, insert a value, and press the Send button. You
should see the same text printed on the console.
The text inserted by the user in the text field is sent to the delegate
method. The framework offers a special class to represent the response
called UNTextInputNotificationResponse. To access the value inserted by the user,
we must cast the response object to this class and then read its userText
property, as we did in Listing 16-20. The result is shown below.

Figure 16-10: Notification with a text field

16.3 Key/Value Observing KVC


 

KVO (Key/Value Observing) is a system we can use to perform a task when For the KVO system to be able to monitor the values of a property, the
the value of a property is modified. This is similar to what the didSet() object in which the property was defined must comply to the
method does for the properties of our own structures and classes (see NSKeyValueCoding protocol. This protocol defines another system called KVC
Property Observers in Chapter 3), with the difference that some predefined (Key/Value Coding) that allows us to access properties using their names as
classes provided by Apple come ready to work with this system and keys. Instead of using dot notation to access the values of a property, we
therefore we can monitor the values of their properties as we do with our call methods defined in this protocol with a string that identifies the
own from anywhere in the code. property we want to modify. The following are the most frequently used.
There are several situations in which KVO may be useful. For example, we
may have a property that reflects the status of an operation. The problem setValue(Any?, forKeyPath: String)—This method assigns a value
is that we do not know when the status is going to change, and which are to the property specified by the forKeyPath argument. The first
the objects that must be notified when a change occurs. Constantly argument is the value we want to assign to the property and the
checking the property from every object to see if its value has changed is
forKeyPath argument is a string with the name of the property.
not practical. KVO allows us to add observers to every object that needs to
keep an eye on the property. Every time the property is modified, the value(forKey: String)—This method retrieves the value of the
system notifies these observers, so the objects can perform the task they property identified by the forKey argument. The argument is a string
need, such as updating the interface or initiating a new operation. with the name of the property.

Some of the frameworks provided by Apple include classes that are ready
to work with KVC, but we can define our own. An easy way to make our
custom classes KVC compliant is to declare them as subclasses of the
NSObject class. This is the class from which the rest of the Objective-C
classes are created, and it already includes an implementation of the
protocol. The following example illustrates how to define a subclass of
NSObject called MyControl. Notice that because KVC is a system developed in
Objective-C the properties must be preceded by the @objc prefix.

Listing 16-21: Defining a custom class to work with KVC



import Foundation The ViewController class in Listing 16-22 creates an instance of the MyControl
class MyControl: NSObject { class and then implements an Action for the Stepper to change the value of
@objc var round = false the round property every time the value of the Stepper is modified. The
}
 Action gets the Stepper’s current value and then sets the value of the round
property to true or false depending on whether the current value is multiple
Because the MyControl class is a subclass of NSObject, we can call the of 10 or not.
NSKeyValueCoding protocol methods on instances of this class to set or
retrieve the value of the round property. In the following example, we
present a ViewController class that creates an instance of MyControl and
modifies the value of the round property every time the user changes the
value of a Stepper. The example assumes that the scene contains an
interface with a Stepper and a label.

Listing 16-22: Modifying property values using KVC



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var counterLabel: UILabel!
var control: MyControl!

override func viewDidLoad() {


super.viewDidLoad()
control = MyControl()
}
@IBAction func updateValue(_ sender: UIStepper) {
let current = Int(sender.value)
if current % 10 == 0 {
control.setValue(true, forKey: "round")
} else {
control.setValue(false, forKey: "round")
}
counterLabel.text = String(current)
}
}

KVO observeValue(forKeyPath: String?, of: Any?, change:


 [NSKeyValueChangeKey : Any]?, context:
UnsafeMutableRawPointer?)—This method is called on the object
when there is a change in the property the object is observing. The
When properties are set using the setValue() method instead of the = sign, as
we did in the last example, they automatically become KVO compliant and forKeyPath argument is a string with the name or the path to the
therefore they may be observed from other objects. The process requires property, the of argument is a reference to the object where the
the objects to become observers. For this purpose, Foundation includes change occurred, the change argument is a dictionary with the
the NSKeyValueObserving protocol, to which the UIViewController class conforms description of the changes, and the context argument is the value
by default. The following are some of the methods defined by the protocol that identifies the observer.
to add and remove observers.
To observe a value, we must add the observer with the addObserver()
addObserver(NSObject, forKeyPath: String, options: method and override the observeValue() method to perform a task when a
NSKeyValueObservingOptions, context: change occurs, as shown next.
UnsafeMutablePointer?)—This method adds an observer to the
object. The first argument is a reference to the observer (usually self), Listing 16-23: Observing the value of a property

the forKeyPath argument is a string with the name or the path to the
import UIKit
property, the options argument is a Set with enumeration values that
determine the values sent to the observer (possible values are new, old, class ViewController: UIViewController {
@IBOutlet weak var counterLabel: UILabel!
initial, and prior), and the context argument is a generic value that var control: MyControl!
identifies the observer (used when a class and its subclasses observe
override func viewDidLoad() {
the same property). super.viewDidLoad()
control = MyControl()
removeObserver(NSObject, forKeyPath: String, context: control.addObserver(self, forKeyPath: "round", options: [], context: nil)
UnsafeMutablePointer?)—This method removes an observer. The }
@IBAction func updateValue(_ sender: UIStepper) {
arguments are the same values specified in the addObserver() method let current = Int(sender.value)
when the observer was added. if current % 10 == 0 {
control.setValue(true, forKey: "round")
} else {
With KVO, an object can observe the property of another object and control.setValue(false, forKey: "round")
}
perform a task when the value of the property changes. For this purpose, counterLabel.text = String(current)
the NSKeyValueObserving protocol defines the following method. }
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change:
[NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "round" { reported, including those performed with traditional methods like the =
if control.round {
counterLabel.textColor = UIColor.red sign, we need to make the property compliant to KVO by implementing
} else { some of the methods provided by the NSKeyValueObserving protocol. The
counterLabel.textColor = UIColor.black
}
simplest way to do this is by calling the following protocol methods from
} the property’s didSet() and willSet() methods.
}
}
 willChangeValue(forKey: String)—This method informs the class
that the value of the property is about to change. The forKey
This example updates the previous ViewController class to add an observer to argument is a string with the property’s name or path.
monitor the value of the round property. When the user modifies the value
didChangeValue(forKey: String)—This method informs the class
of the Stepper, the updateValue() method is called and the value of the round
that the value of the property was changed. The forKey argument is a
property is modified with the setValue() method. This prompts the property
to report the change. The observer added to the ViewController class is string with the property’s name or path.
notified of the change and executes the observeValue() method. In this
method, we check if the name or path of the property matches the name The following example implements these two methods to make the round
or path of the property we want to read and then perform the task property report the changes before and after they happen.
according to its value. In this example, if the value of round is true, we
change the color of the label to red, and if it is false we change it back to Listing 16-24: Reporting changes
black. 
import Foundation

Do It Yourself: Create a new project. Add a Stepper and a label to class MyControl: NSObject {
the scene. Connect the Stepper with an Action called updateValue() and @objc var round = false {
didSet {
the label with an Outlet called counterLabel. Create a Swift file called self.didChangeValue(forKey: "round")
MyControl for the code in Listing 16-21. Complete the ViewController }
willSet {
class with the code in Listing 16-23 and run the application. Press the self.willChangeValue(forKey: "round")
Stepper to change the value. The counterLabel label should turn red }
}
every time the value is multiple of 10. }

Setting the value of the property with the setValue() method prompts the
property to notify the change, but this means that every time we want the Now, we can assign new values to the round property as we always do and
changes in a property to be noticed by the observers we have to perform get the property to report the changes. The following example shows the
those changes calling the setValue() method. If we want every change to be new action for the Stepper.

Listing 16-25: Working with a KVO compliant property


CHAPTER 17 - ICLOUD

@IBAction func updateValue(_ sender: UIStepper) {
let current = Int(sender.value)
if current % 10 == 0 {
control.round = true
} else {
control.round = false
}
counterLabel.text = String(current)
}

Do It Yourself: Update the MyControl class with the code in Listing


16-24 and the updateValue() method of the ViewController class with the
code in Listing 16-25. Run the application. Again, you should see the
label changing colors according to the Stepper’s current value.

IMPORTANT: With Key/Value Observing you can only observe the


properties of one object from another. To observe properties of the
same object you can use Property Observers (see Chapter 3).
Usually, you do not need this technique to observe values in your
own classes, but some frameworks include classes that require the
use of Key/Value Observing, as we will see later.
17.1 Data in the Cloud Enabling iCloud
 

These days, users own more than one device. If we create an application iCloud must be enabled for each application. The system requires
that works on multiple devices, we must provide a way for users to share entitlements to authorize our app to use the service and a container where
their data, otherwise they will have to insert the same information on our app’s data will be stored. Fortunately, Xcode can set up everything for
every device they own. But the only way to do it effectively is through a us by just selecting an option in the Signing & Capabilities panel, found
server. Data from one device is stored on a server so that it can be inside the app’s settings window. The panel includes a + button at the top-
retrieved later from other devices. Setting up a server to run this kind of left corner to add a new capability to the application (Figure 17-1, number
system is complicated and costly. To provide a standard solution, Apple 1). The button opens a view with all the capabilities available, as shown
created a free system called iCloud. iCloud allows applications to below. The capability is added by clicking on it and pressing Return.
synchronize data across devices using Apple servers. It provides three basic
services: Key-Value Storage, to store single values, Document Storage, to Figure 17-1: Activating iCloud for our app
store files, and CloudKit Storage, to store structured data.

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
for our app and Xcode takes care of creating the entitlements. Figure 17-2,
below, shows the panel with the Key-Value storage service activated.

Figure 17-2: Activating iCloud services

Testing Devices

The best way to test iCloud is by running the app in two different devices,
 but Apple has made iCloud services available in the simulator as well.
Thanks to this feature, we can synchronize data between a device and the
simulator to test our app.
Do It Yourself: Create a new project. Click on the app’s settings
For the devices and the simulators to be able to access iCloud services, we
option at the top of the Navigator Area (Figure 5-12, number 6) and
must register our iCloud account in the Settings app. We have to go to the
open the Signing & Capabilities panel. Click on the + button at the
Home screen, access the Settings app, tap on the option Sign in to your
top-left corner to add a capability (Figure 17-1, number 1). Select the iPhone/iPad (Figure 17-3, center), and sign in to Apple’s services with our
iCloud option, press return, and check the option Key-value storage. Apple ID. The process must be repeated for every device or simulator we
This iCloud service is now available in your application. want to use.

Figure 17-3: iCloud account in the simulator


17.2 Key-Value Storage To store or access a value, we must initialize an NSUbiquitousKeyValueStore
object and then call its methods. The object takes care of establishing the

connection with iCloud and downloading or uploading the values. As we
already mentioned, the system is used to storing discrete values that
The Key-Value storage system is the User Defaults system for iCloud. It
represent the user’s preferences or the app’s state. For example, we may
works the same way, but all the data is stored on iCloud servers instead of
have a Stepper that lets the user set a limit on the number of items the
the device. We can use it to store the app’s preferences, states, or any
app can manage.
other value that we need to automatically set on each device owned by the
user. Foundation defines the NSUbiquitousKeyValueStore class to provide access
Figure 17-4: Interface to test Key-Value storage
to this system. The class includes the following methods to store and
retrieve values.

set(Value, forKey: String)—This method stores the value specified


by the first argument with the key specified by the forKey argument.
The class provides versions of this method for every data type we are

allowed to store in the system, such as String, Bool, Data, Double, Int64,
dictionaries, arrays, and Property List values (NSNumber, NSString,
If the user decides to set the limit to 5 in one device, the same limit should
NSDate, NSArray, NSDictionary, NSData, and the equivalents in Swift).
be set when the application is executed on a different device. To achieve
bool(forKey: String)—This method retrieves a value of type Bool. this, we must read the value from iCloud when the view is loaded and
double(forKey: String)—This method retrieves a value of type store it back when the user sets a new one. The following is the view
Double.
controller necessary for this application.

longLong(forKey: String)—This method retrieves a value of type Listing 17-1: Storing setting values in iCloud
Int64. 
string(forKey: String)—This method retrieves a value of type String. import UIKit

array(forKey: String)—This method retrieves an array. class ViewController: UIViewController {


@IBOutlet weak var stepper: UIStepper!
dictionary(forKey: String)—This method retrieves a dictionary. @IBOutlet weak var counter: UILabel!
var kvStorage: NSUbiquitousKeyValueStore!
data(forKey: String)—This method retrieves a value of type Data.
object(forKey: String)—This method retrieves an object. override func viewDidLoad() {
super.viewDidLoad()
kvStorage = NSUbiquitousKeyValueStore()
let control = kvStorage.double(forKey: "control")
stepper.value = control

counter.text = String(control) Listing 17-1. Run the application. Press the Stepper’s button to
}
@IBAction func changeValue(_ sender: UIStepper) { change the value to 5. Wait a few seconds for the information to be
let current = stepper.value uploaded. Stop the application and run it again. The label should
counter.text = String(current)
kvStorage.set(current, forKey: "control") contain the value 5 this time. If you run the app on a different device
kvStorage.synchronize()
}
or simulator, you should see the value 5 again on the screen
} (remember to activate the same iCloud account in any of the

simulators or devices you try).
This example creates the NSUbiquitousKeyValueStore object as soon as the view
is loaded and reads a value of type Double identified with the key "control".
IMPORTANT: The simulator does not update the information
Like it happens with some of the methods included in the UserDefaults class, automatically. Most of the time, you must force the update. If you
the double() method used in this example returns 0 if the value is not found, modify the value on your device and do not see it changing on the
so the first time the application is executed, the value assigned to the label simulator, open the Features menu at the top of the screen and
will be 0. To update the value, we have created an Action for the Stepper select the option Trigger iCloud Sync. This will synchronize the
called changeValue(). In this method, we get the current value of the Stepper, application with iCloud and update the values right away.
assign it to the label to reflect the change on the screen, and then store the
value in iCloud with the "control" key using the set() method. If the user The previous example stores in iCloud a value associated with the "control"
opens the application in a different device, the process starts again by key, and every time the app is executed the value is retrieved from iCloud
reading the value associated with the "control" key from iCloud, but this servers and shown on the screen. The problem is that at this moment the
time instead of returning 0 it will find the last number stored by the set() application does not know when the value was modified from another
method. device. If we launch the app on an iPhone, set the value to 5, and then
Notice that we have executed a method called synchronize() after storing a launch it again on an iPad, we will see the value 5 on the iPad’s screen, but
new value. This is a method provided by the NSUbiquitousKeyValueStore class modifying the value thereafter will not produce any effect on the other
to force the system to send the new value to iCloud right away. It is not device. To report the changes, the NSUbiquitousKeyValueStore class defines the
always necessary, only when we want to make sure that the value is there following notification.
as soon as possible.
didChangeExternallyNotification—This notification is posted by
Do It Yourself: Create a new project. Add a Stepper and a label to the system when a change in the values of the Key-Value storage is
the scene, as illustrated in Figure 17-4. Connect the Stepper to the detected.
ViewController class with an Outlet called stepper and the label with an
Outlet called counter. Connect the Stepper with an Action called In the following example, we listen to this notification to update the value
changeValue() and complete the ViewController class with the code in every time is changed from another device.
The first thing we do in this view controller is to create a task in the
Listing 17-2: Updating the interface when a new value is received viewDidLoad() method to call an asynchronous method that listens to
 didChangeExternallyNotification notifications, so every device running the
import UIKit application knows when the value was modified from another device.
When a notification is received, we read the new value and update the
class ViewController: UIViewController {
@IBOutlet weak var stepper: UIStepper! interface. Now, we can have the application running simultaneously on
@IBOutlet weak var counter: UILabel! different devices and the user will see the value automatically changing on
var kvStorage: NSUbiquitousKeyValueStore!
the screen when it is modified from another device.
override func viewDidLoad() {
super.viewDidLoad()
kvStorage = NSUbiquitousKeyValueStore()
Do It Yourself: Update the ViewController class with the code in
let control = kvStorage.double(forKey: "control") Listing 17-2. Run the application simultaneously in two devices.
stepper.value = control
counter.text = String(control)
Modify the value on one device and check the other to see how the
label changes (it may take up to a few minutes for the data to be
Task(priority: .high) {
await receiveNotifications() transferred from one device to another). If you use the simulator,
} remember to select the option Trigger iCloud Sync from the Features
}
func receiveNotifications() async { menu.
let center = NotificationCenter.default
let name = NSUbiquitousKeyValueStore.didChangeExternallyNotification
for await notification in center.notifications(named: name, object: kvStorage) {
if notification.name == name {
await MainActor.run {
let control = kvStorage.double(forKey: "control")
stepper.value = control
counter.text = String(control)
}
}
}
}
@IBAction func changeValue(_ sender: UIStepper) {
let current = stepper.value
counter.text = String(current)
kvStorage.set(current, forKey: "control")
kvStorage.synchronize()
}
}

17.3 iCloud Documents


The Key-Value storage system was developed to store small values. The
purpose is to allow users to set preferences or configuration parameters
across devices. Although very useful, it presents some limitations,
especially on the amount of data we can store (currently no more than 1
megabyte). If what we need is to store large amounts of data, we can
activate the iCloud Documents option in the Capabilities panel and upload 
files instead.
The name must be unique, and the best way to guarantee this is to use the
Figure 17-5: iCloud Documents service app's bundle identifier. In Figure 17-6, we use the bundle identifier to
create the container for an app called TestiCloud. Once the container is
created, we should press the Refresh button to make sure the information
is uploaded to Apple servers right away (Figure 17-5, number 2). After this,
the container is added and selected as the active container for the
application.
An iCloud container is called ubiquitous container because its content is
shared with other devices and therefore available everywhere. The
FileManager class includes properties and methods to work with a ubiquitous
container. The following are the most frequently used.

url(forUbiquityContainerIdentifier: String?)—This method
The operative system uses a container to store iCloud files. This container is returns the URL of the app's iCloud container. The
a folder in the app's storage space where iCloud files are created. Once a forUbiquityContainerIdentifier argument is the name of the
file is added, modified, or removed from this container, the system container we want to access. The value nil returns the container which
automatically reports the changes to iCloud, so the copies of the app
name matches the Bundle identifier.
running in other devices can modify their own container to stay
synchronized. To create a container for our app, we must press the + evictUbiquitousItem(at: URL)—This method removes the local
button (Figure 17-5, number 1). This opens a window to insert the name. copy of the document at the URL specified by the at argument.

Figure 17-6: iCloud Container


Documents The method must return an object with the document’s data (usually
a Data structure). The forType argument identifies the file's type (by

default, it is determined from the file’s extension).
Although the files are stored in a container in the device and synchronized load(fromContents: Any, ofType: String?)—This method is called
by the system automatically, working with iCloud presents some challenges when the UIDocument object loads the content of the document from
that the FileManager class cannot overcome. The most important issue is the file. The fromContents argument is an object with the content of
coordination. Because of the unreliability of network connections, at any the file (usually a Data structure that is internally turned into an NSData
moment iCloud may find different versions of the same file. Modifications object), and the ofType argument is a string that identifies the file's
that were introduced to a file from one device may not have reached
type (by default, it is determined from the file’s extension).
iCloud and therefore may later conflict with updates introduced from
another device. The application must decide which version of the file to
Once an object is created from our UIDocument subclass, we can manage the
preserve or what data is more valuable when the file is edited from two
file from the asynchronous methods provided by the class. The following
different devices at the same time. These issues are not easy to solve.
are the most frequently used.
Considering all the challenges developers face, Apple added a class to UIKit
called UIDocument. The class includes capabilities like progression reports,
open()—This asynchronous method asks the UIDocument object to
automatic thumbnail generation, undo manager, and others that simplify
open the file and load its content.
working with documents in iCloud.
The UIDocument class was not designed to be implemented directly in our save(to: URL, for: SaveOperation)—This asynchronous method
code; it is like an interface between the app’s data and the files we use to asks the UIDocument object to save the content of the document on file.
store it. To take advantage of this class, we must create a subclass and The for argument is an enumeration called SaveOperation that indicates
override its methods. Once we define the subclass, we can create the the type of operation to perform. The values available are forCreating
object with the following initializer. (to save the file for the first time) and forOverwriting (to overwrite the
file’s current version).
UIDocument(fileURL: URL)—This initializer creates a new
close()—This asynchronous method saves any pending changes and
UIDocument object. The fileURL argument is a URL structure with the
closes the document.
location of the file in the iCloud's container.

The following are the methods we need to override in the UIDocument


subclass to provide the data for the file and to retrieve it later.

contents(forType: String)—This method is called when the


UIDocument object needs to store the content of the document on file.

Metadata Query result(at: Int)—This method returns the NSMetadataItem object from
the query's results array at the index specified by the at argument.

start()—This method initiates the query.
Accessing the files is also complicated in iCloud. We cannot just get a list of stop()—This method stops the query.
files with methods like contentsOfDirectory() from the FileManager class because
enableUpdates()—This method enables query updates.
there could be some files that have not been downloaded yet to the
device. What we can do instead is to get the information pertaining to the disableUpdates()—This method disables query updates.
files. This data is called metadata, and refers to all the information
associated with a particular file, such as its name, the date it was created, The NSMetadataQuery class also includes some notifications to report when
etc. To get the files' metadata, Foundation defines the NSMetadataQuery class. new data is available. The following are the most frequently used.
This class provides the properties and methods necessary to retrieve the
information and watch for updates. NSMetadataQueryDidUpdate—This notification is posted when
the results of the query changed.
predicate—This property sets or returns the predicate for the query. NSMetadataQueryDidFinishGathering—This notification is
It is an optional of type NSPredicate. posted when the query finishes retrieving all the information.
sortDescriptors—This property sets or returns the sort descriptors
for the query. It is an array of NSSortDescriptor objects. The results of a query are returned by the results property in the form of an
array of NSMetadataItem objects. This is a simple class created to contain the
searchScopes—This property sets or returns a value that indicates
arguments of a file. The class provides the following method to retrieve the
the scope of the query. It is an array with constants that represent a
values.
predefined scope. The constants available for iOS are
NSMetadataQueryUbiquitousDocumentsScope (searches all files in the
value(forAttribute: String)—This method returns the value of the
Documents directory of the iCloud’s container) and file’s attribute determined by the forAtttribute argument. The
NSMetadataQueryUbiquitousDataScope (searches all the files that are not in
NSMetadataItem class defines a list of constants to represent the
the Documents directory of the iCloud’s container). attributes. The constants available are NSMetadataItemFSNameKey (file’s
results—This property returns an array with the query’s results. By name), NSMetadataItemDisplayNameKey (document’s name),
default, the array contains NSMetadataItem objects with the metadata of NSMetadataItemURLKey (file’s URL), NSMetadataItemPathKey (file’s path),
every file found. NSMetadataItemFSSizeKey (file’s size), NSMetadataItemFSCreationDateKey

resultCount—This property returns the number of results produced (date), and NSMetadataItemFSContentChangeDateKey (date the file was last
by the query. modified).
Single Document identifier on top of the iCloud section). If the name of the container
appears in red, press the Refresh button to upload the information

to Apple servers (Figure 17-5, number 2).
The interface for an application capable of processing documents must
include a way for the user to select the document and the tools to edit its The first step to set up the system is to create the UIDocument subclass that
content. For the following example, we will work with only one document is going to take care of our documents. As always, it is recommendable to
to keep it simple. The initial scene includes a button connected to a second create a separate file for the subclass. For this example, we call it
MyDocument.
scene containing a Text View to edit the document’s content.

Figure 17-7: Interface to edit a document Listing 17-3: Creating the document

import UIKit

class MyDocument: UIDocument {


var fileContent: Data?

override func contents(forType typeName: String) throws -> Any {


return fileContent ?? Data()
}
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let data = contents as? Data, !data.isEmpty {
 fileContent = data
}
}
Do It Yourself: Create a new project. Embed the scene in a }

Navigation Controller. Add a button to the scene. Add a second
scene to the Storyboard. Connect the button with a Show segue to
The subclass needs at least three elements: a property to temporarily store
the second scene. Add a bar button with the title Save to the the file's content, the contents() method to provide the data to store on file,
Navigation Bar of the second scene. Add a Text View to the second and the load() method to get the data from the file. When the UIDocument
scene. Click on the app’s settings option at the top of the Navigator object is asked to store or load the data in the file, it calls these methods
Area and open the Signing & Capabilities panel. Click on the + button and use the property as a proxy to move the data around.
at the top-left corner to add a capability. Select the iCloud option The only purpose of our initial scene is to provide the button to open the
and check the option iCloud Documents (Figure 17-5). Press the + editor, so all the code for our example goes in the view controller of the
button to add a container. Insert the app's bundle identifier for the second scene. We called this view controller EditionViewController. In this
container's name and press the OK button (you can find the bundle class, we must get the file and create the MyDocument object to represent it.
We begin by defining the properties and the initial statements.

configured to look into the iCloud’s Documents directory with the


Listing 17-4: Loading the document NSMetadataQueryUbiquitousDocumentsScope variable because this is the
 recommended directory for our files. After the NSMetadataQuery object is set,
import UIKit we start the process with the start() method.
When the NSMetadataQuery object finishes gathering the data, it posts an
class EditionViewController: UIViewController {
@IBOutlet weak var mycontent: UITextView! NSMetadataQueryDidFinishGathering notification. In the receiveMetadata() method,
var document: MyDocument! we build a loop to get these notifications and call another asynchronous
var metaData: NSMetadataQuery!
method to process the data received. The following is the implementation
override func viewDidLoad() { of this method.
super.viewDidLoad()
Task(priority: .high) {
await receiveMetadata() Listing 17-5: Processing query results
} 
metaData = NSMetadataQuery()
metaData.predicate = NSPredicate(format: "%K == %@", NSMetadataItemFSNameKey, func metadataReceived(notification: NSNotification) async {
"myfile.dat") if metaData.resultCount > 0 {
metaData.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope] let file = metaData.result(at: 0) as! NSMetadataItem
metaData.start() let fileURL = file.value(forAttribute: NSMetadataItemURLKey) as! URL
} document = MyDocument(fileURL: fileURL)
func receiveMetadata() async {
let center = NotificationCenter.default let success = await document.open()
let name = NSNotification.Name.NSMetadataQueryDidFinishGathering if success {
for await notification in center.notifications(named: name, object: nil) { if let data = self.document.fileContent {
await metadataReceived(notification: notification as NSNotification) await MainActor.run {
} self.mycontent.text = String(data: data, encoding: .utf8)
} }
} }
 }
} else {
let content: String = await MainActor.run {
Because of the logic of our application, we need a property to store the let text = self.mycontent.text ?? ""
reference to the MyDocument object we are going to use to access the file, return text
}
and a property to reference the NSMetadataQuery object we need to search await initializeDocument(content: content)
for the file in iCloud. In Listing 17-4, we call these properties document and }
}
metaData.
func initializeDocument(content: String) async {
The first thing we do in our view controller is to call an asynchronous let manager = FileManager.default
method to listen to notifications, and then initialize the NSMetadataQuery if let fileURL = manager.url(forUbiquityContainerIdentifier: nil) {
let documentURL = fileURL.appendingPathComponent("Documents/myfile.dat")
object. This object checks whether the file exists and gets the metadata.
The object's predicate is set to look for a specific file called myfile.dat, if let data = content.data(using: .utf8) {
document = MyDocument(fileURL: documentURL)
which is the only file managed by this application, and the query was
document.fileContent = data method for the Save button and some auxiliary methods to save and close
if manager.fileExists(atPath: documentURL.path) {
await document.save(to: documentURL, for: .forOverwriting) the document.
} else {
await document.save(to: documentURL, for: .forCreating)
}
Listing 17-6: Saving the document
} 
} @IBAction func saveDocument(_ sender: UIBarButtonItem) {
} Task(priority: .high) {
 await storeDocument()
await closeDocument()
The receiveMetadata() method of Listing 17-5 checks whether the query found await MainActor.run {
closeScene()
a file or not by reading the resultCount property. If this property returns a }
value greater than 0, we get the first element with the result() method (we }
}
are only working with one file), get its URL with the value() method, and use func storeDocument() async {
it to create the MyDocument object. This tells the MyDocument object what is let content: String = await MainActor.run {
the file's URL, but we still need to call the open() method to open it. This let text = self.mycontent.text ?? ""
return text
method asks the instance of MyDocument to load the content of the file. The }
instance then executes its load() method and assigns the content to its let manager = FileManager.default
if let fileURL = manager.url(forUbiquityContainerIdentifier: nil) {
fileContent property. The data is then converted into a string with the String let documentURL = fileURL.appendingPathComponent("Documents/myfile.dat")
initializer and assigned to the Text View to show it on the screen. if let data = content.data(using: .utf8) {
document.fileContent = data
On the other hand, if the file does not exist, we call another asynchronous
await document.save(to: documentURL, for: .forOverwriting)
method to generate the URL and create a new file. In this case, we take }
advantage of a method provided by the FileManager class to get iCloud }
}
locations called url(forUbiquityContainerIdentifier:). This method returns the URL func closeDocument() async {
of the location of our app’s iCloud container. Because an app can have await document.close()
}
several containers, the method takes an argument to specify the func closeScene() {
container’s identifier (declared as nil to use the container by default). The navigationController?.popViewController(animated: true)
URL returned by this method is the root directory, so we append the }
override func viewDidDisappear(_ animated: Bool) {
Documents directory and the file’s name to create the file. With the final Task(priority: .high) {
URL, we create a MyDocument object, check if the file exists, and call the save() await closeDocument()
}
method to overwrite it or create it, respectively. Internally, the MyDocument }
instance calls its contents() method to get the content and create the file. 
The codes added so far to the EditionViewController class load or create a file
and show its content on the screen, but we still need to store the changes The saveDocument() method gets the file's URL again, and then calls the
when the document is saved by the user. The following is the Action storeDocument() method to store the document, the closeDocument() method to

close it, and the closeScene() method to remove the scene. In the Multiple Documents
storeDocument() method, we get the text from the Text View, assign it to the

fileContent property of the MyDocument object, and then call the save() method
to save it on file. The operation assigned to the method is forOverwriting
The initial scene of our previous example offers a single button to open a
because at this point, we already know that there is a file available, and we
document and display its content on the screen, however, most
just need to update its content. To make sure that the document is always
applications allow users to create and manage all the documents they
closed, we call the closeDocument() method after the document is saved and
need. An NSMetadataQuery object generates a live query. The query keeps
when the scene is remove (the user pressed the Back button).
looking for documents until we tell it to stop, and this is how we work with
multiple documents in iCloud. To illustrate how the process works, we will
Do It Yourself: Create a new file with a subclass of UIDocument called
replace the initial scene of our example with a Table View Controller, as
MyDocument and complete the class with the code in Listing 17-3.
shown next.
Create a subclass of UIViewController called EditionViewController and
assign it to the second scene. Connect the Text View with an Outlet Figure 17-8: Interface to work with multiple documents
called mycontent. Complete the class with the code in Listing 17-4 and
the methods in Listing 17-5. Connect the Save button with an action
called saveDocument() and complete the method and the class with the
code in Listing 17-6. Run the application. Press the Edit Document
button, insert some text in the Text View and press the Save button.
Run the app on a different device and press the Edit Document
button again. You should see the text inserted before on the screen.
If you use the simulator, remember to select the Trigger iCloud Sync

option from the Features menu to force the synchronization.
Do It Yourself: Delete the initial scene of the interface introduced
in Figure 17-7. Add a Table View Controller in its place. Connect the
Navigation Controller to the Table View Controller with a Root View
Controller segue. Connect the Table View Controller to the second
scene with a Show segue and give the segue the identifier
"showDocument". Add a bar button to the Table View Controller
with the title Add Document. Keep the design of the second scene
the same as before.
The Table View Controller must create a query to get the documents
available and then show their names on the table. We called this class Because we are not only getting the list of documents available but also
DocumentsViewController. The following code defines the properties we need monitoring iCloud for changes, we check two notifications: the
to manage the data and configure the query. NSMetadataQueryDidFinishGathering notification to know when the process of
gathering the initial data is over, and the NSMetadataQueryDidUpdate
Listing 17-7: Initiating the query notification to know when updates become available. In the view
 controller of Listing 17-7, we call a method to perform two tasks, one for
import UIKit each notification. When a notification is received, we call the updateList()
method to update the table with the current files.
class DocumentsViewController: UITableViewController {
var dataSource: UITableViewDiffableDataSource<Int, String>!
var documentsList: [String] = [] Listing 17-8: Keeping the table up to date
var metaData: NSMetadataQuery!

override func viewDidLoad() { func updateList(notification: NSNotification) {
super.viewDidLoad() metaData.disableUpdates()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell") for item in metaData.results as! [NSMetadataItem] {
prepareDataSource() let name = item.value(forAttribute: NSMetadataItemFSNameKey) as! String
prepareSnapshot() if documentsList.firstIndex(of: name) == nil {
documentsList.append(name)
receiveMetadata() }
}
metaData = NSMetadataQuery() documentsList.sort(by: { (value1, value2) in value1 < value2 })
metaData.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope] metaData.enableUpdates()
metaData.start()
} Task(priority: .high) {
func receiveMetadata() { await MainActor.run {
let center = NotificationCenter.default prepareSnapshot()
Task(priority: .high) { }
let name1 = NSNotification.Name.NSMetadataQueryDidFinishGathering }
for await notification in center.notifications(named: name1, object: nil) { }
updateList(notification: notification as NSNotification) 
}
}
Task(priority: .high) { Every time the query gets information, it posts notifications to let our code
let name2 = NSNotification.Name.NSMetadataQueryDidUpdate know what happened. To avoid conflicts with the data being processed and
for await notification in center.notifications(named: name2, object: nil) {
updateList(notification: notification as NSNotification)
the new data received, we must pause the query until the process is over.
} This is done by the disableUpdates() and enableUpdates() methods of the
}
NSMetadataQuery class. In the code of Listing 17-8, we call these methods at
}
} the beginning and the end to make sure that the updateList() method is not
 executed again until we get the new information and refresh the table.

Usually, an application like this gets the values it needs from the results Every time a cell is selected, we must send the file's name to the second
returned by the query and stores them in a place the rest of the view scene. For this purpose, we need to implement the tableView(UITableView,
controller can read, such as a property, a model, or a more complex didSelectRowAt:) method to perform the segue and the prepare() method to
storage system like Core Data. For this example, we defined a single send the value.
property called documentsList to contain an array with the names of the
documents. Because the updateList() method is called when the query Listing 17-10: Sending the name of the selected document to the second
finishes gathering the data and also when it finds changes, we need to scene
check for duplicates before introducing a new name into this array. The 
code in Listing 17-8 creates a loop to go through every new value, gets the override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
attribute for the name, and checks if it already exists in the array with the performSegue(withIdentifier: "showDocument", sender: self)
}
firstIndex(of:) method. Comparing the value returned by this method to nil, override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
we make sure that there are no duplicates on the list. After the list is if segue.identifier == "showDocument" {
let controller = segue.destination as! EditionViewController
updated, the snapshot is recreated, and the new information is shown on if let path = tableView.indexPathForSelectedRow {
the screen. if let item = dataSource.itemIdentifier(for: path) {
controller.selected = item
The methods to create the diffable data source and the snapshot don't }
require anything new, but for simplicity, we are going to use an integer to }
identify the sections (Integers conform to the Hashable protocol). }
}

Listing 17-9: Populating the table
 The DocumentsViewController class is not complete until we include the code
func prepareDataSource() { necessary for the user to create new documents. To simplify this example,
dataSource = UITableViewDiffableDataSource<Int, String>(tableView: tableView) { tableView,
we decided to create an Alert View with a Text Field to insert the name of
indexPath, item in
let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) the new document. The following example defines the Action for the Add
var config = cell.defaultContentConfiguration() Document button required to create the new document and store it in
config.text = item
cell.contentConfiguration = config iCloud.
return cell
}
}
Listing 17-11: Creating new documents
func prepareSnapshot() { 
var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
@IBAction func addDocument(_ sender: UIBarButtonItem) {
snapshot.appendSections([0])
let alert = UIAlertController(title: "New File", message: nil, preferredStyle: .alert)
snapshot.appendItems(documentsList)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
dataSource.apply(snapshot)
alert.addAction(cancel)
}
let action = UIAlertAction(title: "Create", style: .default, handler: { (action) in

if let fields = alert.textFields, let file = fields.first?.text {
if !file.isEmpty && self.documentsList.firstIndex(of: file) == nil {
Task(priority: .high) { In the EditionViewController class, we do not need to create a new document
await self.createNewDocument(fileName: file)
await MainActor.run { anymore, we just have to read the selected document, show it on the
self.prepareSnapshot() screen, and save the changes introduced by the user.
}
}
} Listing 17-12: Reading and modifying a document
} 
})
alert.addAction(action) import UIKit
alert.addTextField(configurationHandler: { (textField) in
textField.placeholder = "Insert name and extension" class EditionViewController: UIViewController {
}) @IBOutlet weak var mycontent: UITextView!
present(alert, animated: true, completion: nil) var document: MyDocument!
} var documentURL: URL!
func createNewDocument(fileName: String) async { var selected: String!
let manager = FileManager.default
if let fileURL = manager.url(forUbiquityContainerIdentifier: nil) { override func viewDidLoad() {
let documentURL = fileURL.appendingPathComponent("Documents/\(fileName)") super.viewDidLoad()
let document = MyDocument(fileURL: documentURL) Task(priority: .high) {
await document.save(to: documentURL, for: .forCreating) await openDocument()
} }
} }
 func openDocument() async {
let manager = FileManager.default
if let fileURL = manager.url(forUbiquityContainerIdentifier: nil) {
The code in Listing 17-11 creates an Alert View with a Text Field and a documentURL = fileURL.appendingPathComponent("Documents/\(selected!)")
button with the title Create. When this button is pressed, we get the text in document = MyDocument(fileURL: documentURL)
let success = await document.open()
the Text Field (the first in the array returned by the textFields property), if success {
confirm that the string is not empty, check if a file with that name already if let data = self.document.fileContent {
await MainActor.run {
exists, and if not, call an asynchronous method to create the new self.mycontent.text = String(data: data, encoding: .utf8)
document and save it. }
}
}
Do It Yourself: Create a subclass of UITableViewController called }
DocumentsViewControllerand assign it to the Table View Controller }
@IBAction func saveDocument(_ sender: UIBarButtonItem) {
added as the initial scene. Update the class with the code in Listing Task(priority: .high) {
await storeDocument()
17-7. Add the methods in Listings 17-8, 17-9, 17-10 and 17-11 to the await closeDocument()
class, and connect the Add Document button to the addDocument() await MainActor.run {
closeScene()
Action. }
}
}

func storeDocument() async {


let content: String = await MainActor.run {
let text = self.mycontent.text ?? "" IMPORTANT: In these examples, we have stored only a single string
return text in the documents, but we can archive all the values we want,
}
if let data = content.data(using: .utf8) { including custom classes, as we did in the examples in Chapter 15.
document.fileContent = data When working with documents that manage content of several files,
await document.save(to: documentURL, for: .forOverwriting)
} like text files and image files, instead of encoding all together in one
} file it is recommended to use a file wrapper. The wrapper is created
func closeDocument() async {
await document.close() with objects of the NSFileWrapper class. The topic goes beyond the
}
scope of this book. For more information, visit our website and
func closeScene() {
navigationController?.popViewController(animated: true) follow the links for this chapter.
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
Task(priority: .high) {
await closeDocument()
}
}
}

When this scene is loaded, it opens the file with the name specified by the
selected property, reads its content, creates the string from the data, and
assigns it to the Text View to show the content on the screen. If the user
taps the Save button, the process is inverted; we convert the text into data
and save the file.

Do It Yourself: Update the EditionViewController class with the code in


Listing 17-12. Run the application. Press the Add Document button.
Insert the name and extension of the document you want to create,
and press Create. Run the application on a different device. The
document you just created should appear on the device’s screen.
Select the document, insert some text, and press the Save button.
After a moment, the document should contain the same text on
both devices.
17.4 CloudKit CloudKit requires a container to manage the databases. If the container is
not automatically generated by Xcode when we activate the CloudKit

service, we must create it ourselves, as we did before for the iCloud
Documents service.
CloudKit is a database system in iCloud. Using this system, we can store
Because CloudKit uses Remote Notifications to report changes in the
structured data online with different levels of accessibility. The system
databases, when we activate CloudKit, Xcode automatically activates an
offers three types of databases to determine who has access to the
additional service called Push Notifications.
information.
Figure 17-10: Push Notifications
Private Database to store data that is accessible only to the
user.
Public Database to store data that is accessible to every user 
running the app.
Shared Database to store data the user wants to share with Remote Notifications are like the Local Notifications introduced in Chapter
other users. 16, but instead of being posted by the app they are sent from a server to
inform our app or the user that something changed or needs attention.
As we did with the rest of the iCloud services, the first step to use CloudKit The Remote Notifications posted by CloudKit are sent from Apple servers
is to activate it from the Signing & Capabilities panel. when something changes in the databases. Because this may happen not
only when the user is working with the app but also when the app is in the
Figure 17-9: CloudKit service background, to get these notifications, we must add the Background Mode
capability and activate two services called Background Fetch and Remote
Notifications.

Figure 17-11: Background Mode

Container

When CloudKit is activated from the Capabilities panel, Xcode creates


entitlements to authorize the app to use the service and the container for
the app's databases. The container is like a space in Apple's servers
designated to our app. The CloudKit framework provides a class called
CKContainer to access the container and the databases. Because an app may
have more than one container, the class includes an initializer to get a
reference to a specific container and also a type method to get a reference
to the container by default.

CKContainer(identifier: String)—This initializer creates the
CKContainer object that references the container identified with the
Do It Yourself: Create a project. Click on the settings option at the
top of the Navigator Area and open the Signing & Capabilities panel. string specified by the identifier argument. It is required when
Click on the + button at the top-left corner to add a capability. Select working with multiple containers or the container's name is different
the iCloud option and press return. Repeat the process to add the from the Bundle's identifier.
Background Modes capability. In the Background Modes section, default()—This type method returns the CKContainer object that
check the options Background fetch and Remote Notifications references the container by default (the container which name is
(Figure 17-11). In the iCloud section, check the option CloudKit. equal to the Bundle's identifier).
Press the + button to add a container (see Figure 17-5, number 1),
insert the app's bundle identifier as the container's name, and press Container objects provide the following properties to get access to each
the OK button (you can find the bundle identifier at the top of the database.
panel). If the name of the container appears in red, press the Refresh
privateCloudDatabase—This property returns a CKDatabase object
button to upload the information to Apple servers (see Figure 17-5,
with a reference to the user's Private database.
number 2).
publicCloudDatabase—This property returns a CKDatabase object
with a reference to the app's Public database.
sharedCloudDatabase—This property returns a CKDatabase object
with a reference to the user's Shared database.
Records
CloudKit databases have specific purposes and functionalities. The
Private database is used when we want the user to be able to share private 
information among his or her own devices (only the user can access the
information stored in this database), and the Public and Shared databases Once we have decided which database we are going to use, we must
are used to share information between users. (The information stored in generate records to store the user's data. Records are objects that store
the Public database is accessible to all the users running our app and the information as key/value pairs. These objects are classified by types to
information stored in the Shared database is accessible to the users with determine the characteristics of the record. For example, if we want to
whom the user decides to share the data.) store records that contain information about books, we can use the type
The data is stored in the database as records and records are stored in "Books" (a type is analog to the Entities in Core Data). The framework
zones. The Private and Public databases include a default zone, but the provides the CKRecord class to create and manage records.
Private and Shared databases also work with custom zones (called Shared
Zones in a Shared database), as illustrated below.
CKRecord(recordType: String, recordID: CKRecord.ID)—This
initializer creates a CKRecord object of the type and with the identifier
Figure 17-12: Database configuration
specified by the arguments. The recordType argument is the type's
identifier, and the recordID argument is the record's identifier.

Records are identified with an object that includes a name and a reference
to the zone the record belongs to (if a custom identifier is not specified,
the record is stored with an identifier generated by CloudKit). To create and
access the identifier and its values, the CKRecord class defines the ID class
with the following initializers and properties.

CKRecord.ID(recordName: String)—This initializer creates a
object to identify a record. The recordName argument is
CKRecord.ID
the name we want to give to the record.
CKRecord.ID(recordName: String, zoneID: CKRecordZone.ID)
—This initializer creates a CKRecord.ID object to identify a record
stored with the name and in the zone specified by the arguments. The
recordName argument is the name we want to give to the record, and
the zoneID argument is the identifier of the custom zone where we
want to store the record.

recordName—This property returns a string with the name of the as a generic CKRecordValue type that we must cast to the right data
record. type.
zoneID—This property returns a CKRecordZone.ID object with the
identifier of the zone to which the record belongs.

The CKRecord class offers properties to set or get the record's identifier and
other attributes. The following are the most frequently used.

recordID—This property returns the CKRecord.ID object that identifies


the record.
recordType—This property returns a string that determines the
record's type.
recordChangeTag—This property returns a string with the tag
assigned to the record (each record is assigned a tag by the server).
creationDate—This property returns a Date value with the date in
which the record was created.
modificationDate—This property returns a Date value that indicates
the last time the record was modified.

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.

setObject(Value?, forKey: String)—This method sets or updates a


value in the record. The fist argument is the value we want to store,
and the forKey argument is the key we want to use to identify the
value. The value must be of any of the following types: NSString,
NSNumber, NSData, NSDate, NSArray, CLLocation, CKAsset, and Reference.

object(forKey: String)—This method returns the value associated


with the key specified by the forKey argument. The value is returned
Zones Query
 

As illustrated in Figure 17-12, the Public database can only store records in When we want to access data stored in CloudKit, we must download the
the 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 identifiers or in a batch using a
(although they are required for synchronization, as we will see later). Zones query. To define a query, the framework provides the CKQuery class. The
are like sections inside a database to separate records that are not related. class includes the following initializer and properties.
For example, we may have an app that stores locations, like the names of
cities and countries, but also allows the user to store a list of Christmas CKQuery(recordType: String, predicate: NSPredicate)—This
gifts. In cases like this, we can create a zone to store the records that initializer creates a CKQuery object to fetch multiple records from a
include information about cities and countries and another zone to store database. The recordType argument specifies the type of records we
the records that include information about the gifts. The CloudKit want to fetch, and the predicate argument determines the matching
framework provides the CKRecordZone class to represent the zones. The
criteria we want to use to select the records.
class includes an initializer to create new 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 in the database. returned by the query.

Operations reference to the record we want to store.


 deleteRecord(withID: CKRecord.ID)—This asynchronous method
deletes from the database the record with the identifier specified by
CloudKit is an online service and therefore any task may take time to the withID argument.
process. For this reason, the CloudKit framework uses asynchronous
operations to access the information on the servers. An operation must be The following are the methods provided by the CKDatabase class to process
created for every process we want to perform on a database, including zones.
storing, reading, and organizing records. These operations are like Swift
asynchronous tasks but created from classes defined in the Foundation recordZone(for: CKRecordZone.ID)—This asynchronous method
framework. The CloudKit framework includes its own subclasses of the fetches the zone with the ID specified by the for argument. The
Foundation classes to define operations. There is a base class called method returns a CKRecordZone object representing the zone that was
CKDatabaseOperation, and then several subclasses for every operation we
fetched or an error if the zone is not found.
need. Once an operation is defined, it must be added to the CKDatabase
object that represents the database we want to modify. The CKDatabase
allRecordZones()—This asynchronous method fetches all the zones
class offers the following method for this purpose. available in the database. The method returns an array of
CKRecordZone objects representing the zones that were fetched or an

add(CKDatabaseOperation)—This method executes the operation error if no zones are found.


specified by the argument. The argument is an object of a subclass of save(CKRecordZone)—This asynchronous method creates a zone in
the CKDatabaseOperation class. the database. The argument is the object representing the zone we
want to create.
Although we can create single operations and assign them to the database, deleteRecordZone(withID: CKRecordZone.ID)—This
as we will see later, the CKDatabase class also offers convenient methods to
asynchronous method deletes from the database the zone with the ID
generate and execute the most common. The following are the methods
specified by the withID argument.
available to process records.

To query multiple records, the CKDatabase class includes the following


record(for: CKRecord.ID)—This asynchronous method fetches the
convenient method.
record with the ID specified by the for argument. The method returns
a CKRecord object with the record or an error if the record is not records(matching: CKQuery, inZoneWith: CKRecordZone.ID?,
found.
desiredKeys: [CKRecord.FieldKey]?, resultsLimit: Int)—This
save(CKRecord)—This asynchronous method stores a record in the asynchronous method performs the query specified by the matching
database (if the record exists, it updates its values). The argument is a argument in the zone specified by the inZoneWith argument. The
desiredKeys argument is an array of the values we want the records Batch Operations
to include, and the resultsLimit argument determines the number of

records we want to fetch (the value 0 returns all the records that
match the query). The method returns a tuple with two values called Although the methods provided by the CKDatabase class are very convenient
matchResults and queryCursor. The matchResults value is an array of tuples and easy to implement, they only perform one request at a time. The
with two values: the record identifier and a Result with the records and problem is that CloudKit servers have a limit on the number of operations
errors found. On the other hand, the queryCursor value is a cursor we we can perform per second (currently 40 requests per second per user are
can use to fetch more records that match this query. allowed), so if our application relies heavily on CloudKit, at one point some
of the requests might be rejected. The solution is to create operations that
records(continuingMatchFrom: Cursor, desiredKeys:
perform multiple requests at once. The CloudKit framework defines three
Dictionary, resultsLimit: Int)—This asynchronous method creates
subclasses of the CKDatabaseOperation class for this purpose. The
an operation that fetches records starting from the cursor specified by
CKModifySubscriptionsOperation class creates an operation to add or modify
the continuingMatchFrom argument. The cursor is an object that subscriptions, the CKModifyRecordZonesOperation class is used to add or
configures a query to retrieve the remaining results of a previous modify record zones, and the CKModifyRecordsOperation class is for adding
query. The desiredKeys argument is an array of the values we want and modifying records.
the records to include, and the resultsLimit argument determines the
number of records we want to fetch (the value 0 returns all the CKModifySubscriptionsOperation(subscriptionsToSave:
records that match the query). The method returns a tuple with two [CKSubscription], subscriptionIDsToDelete:
values called matchResults and queryCursor. The matchResults value is an [CKSubscription.ID])—This initializer returns an operation that adds
array of tuples with two values: the record identifier and a Result or modifies one or more subscriptions. The subscriptionsToSave
enumeration value with the records and errors found. On the other argument is an array with the subscriptions we want to add or modify,
hand, the queryCursor value is a cursor we can use to fetch more and the subscriptionIDsToDelete is an array with the identifiers of the
records that match this query. subscriptions we want to delete from the server.
CKModifyRecordZonesOperation(recordZonesToSave:
[CKRecordZone], recordZoneIDsToDelete: [CKRecordZone.ID])
—This initializer returns an operation that adds or modifies one or
more record zones. The recordZonesToSave argument is an array with
the record zones we want to add or modify, and the
recordZoneIDsToDelete is an array with the identifiers of the record
zones we want to delete from the server.

CKModifyRecordsOperation(recordsToSave: [CKRecord], References


recordIDsToDelete: [CKRecord.ID])—This operation adds or 
modifies one or more records. The recordsToSave argument is an
array with the records we want to add or modify, and the Records of different types are usually related. For example, along with
recordIDsToDelete argument is an array with the identifiers of the records of type Countries we may have records of type Cities to store
records we want to delete from the server. information about the cities of each country. To create these relationships,
records may include references. References are objects that store
The operations must be initialized and then added to the database with information about a connection between one record and another. They are
the add() method of the CKDatabase class. Each initializer offers the options created from the Reference class defined inside the CKRecord class. The
to modify elements and remove them. If we only need to perform one following are the Reference initializers.
task, the other can be declared as nil.
CKRecord.Reference(recordID: CKRecord.ID, action:
CKRecord.ReferenceAction)—This initializer creates a Reference
object pointing to the record identified with the ID specified by the
recordID argument. The action argument is an enumeration that
determines what the database should do with the record when the
record that is referencing is deleted. The possible values are none
(nothing is done) and deleteSelf (when the record referenced by the
reference is deleted, the record with the reference is deleted as well).
CKRecord.Reference(record: CKRecord, action:
CKRecord.ReferenceAction)—This initializer creates a Reference
object pointing to the record specified by the record argument. The
action argument is an enumeration that determines what the
database should do with the record when the record that is
referencing is deleted. The possible values are none (nothing is done)
and deleteSelf (when the record referenced by the reference is deleted,
the record with the reference is deleted as well).

References in CloudKit are called Back References because they are


assigned to the record that is the children of another record. Following our
example, the reference should be assigned to the city and not the country, CloudKit Dashboard
as illustrated next.

Figure 17-13: Back references
CloudKit creates a model of our app's database on its servers as records
are added to the database. For example, if our app stores records of type
"Books", the first time a record is created, CloudKit adds the type "Books"
to the list of record types available for our app and creates fields to
represent each of the record's values. This way, the system sets up the
database's structure from the data we store during development, saving us
the trouble of configuring the database beforehand (we do not have to
create a model as we do with Core Data). But there are some configuration
parameters that the system cannot determine and we need to set up
ourselves. For this purpose, Apple provides the CloudKit dashboard. This is
an online control panel that we can use to manage the CloudKit databases,
add, update, or remove records, and configure the model.

The panel is available at icloud.developer.apple.com/dashboard/ or by
clicking on the CloudKit Dashboard button at the bottom of the iCloud
section in the Signing & Capabilities panel. The dashboard's home page
includes four buttons to access the tools available. We can configure the
database, check how our database is performing, check user activity, and
manage our account.

Figure 17-14: Main menu

If we click on the CloudKit Database button, a panel is loaded to edit the modify others that the app may need later. For this purpose, the
database. The panel includes an option at the top to select the container dashboard provides access to the schema on the left-side bar.
(Figure 17-15, number 1), a bar on the left to edit the data and the schema
(the model), and a bar on the right to show and edit the values. Figure 17-16: Database schema

Figure 17-15: Database panel



From the Record Types option, we can add, modify, or delete record types
The bar on the left includes a button to select from two configurations:
(Entities) and their values (Attributes). The option to add a new record type
Development or Production (Figure 17-15, number 2). The Development
is at the top of the panel (Figure 17-17, number 1), and the record types
option shows the configuration of the database we use during
already created are listed below (Figure 17-17, number 2).
development. This is the database we use while we are developing our
app. The Production option shows the configuration of the database that
Figure 17-17: Record types
we are going to deliver with our app (the one that is going to be available
to our users).
During development, we can store information in the database for testing.
Below the configuration option is the Data section (Figure 17-16, number
3) where we can edit the data store by our application, including records,
zones, and subscriptions. The panel also offer an option on the right to add 
records (Figure 17-15, number 4), and a list of values to edit.
As we already explained, the schema (the database model) is automatically
created by CloudKit servers when we save records from our app during
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
Implementing CloudKit database, and define the diffable data sources. As always, we call it
ApplicationData.

Listing 17-13: Creating the model
To illustrate how to work with CloudKit databases, we are going to create a

simple application that allows the user to save a list of locations. The
import UIKit
interface presents two Table View Controllers inside a Navigation import CloudKit
Controller to insert and list the names of countries and cities.
enum Sections {
case main
Figure 17-18: Interface to work with CloudKit }
class ApplicationData {
var database: CKDatabase!
var selectedCountry: CKRecord.ID!

var dataSourceCountries: UITableViewDiffableDataSource<Sections, CKRecord.ID>!


var listCountries: [CKRecord] = []

var dataSourceCities: UITableViewDiffableDataSource<Sections, CKRecord.ID>!


var listCities: [CKRecord] = []

init() {
let container = CKContainer.default()
database = container.privateCloudDatabase
 }
}
Do It Yourself: Create a new project. Remove the initial scene and var AppData = ApplicationData()

add a Table View Controller. Embed the scene in a Navigation
Controller. Assign the Navigation Controller as the initial scene from Besides the properties to store the diffable data sources for the countries
the Attributes Inspector panel. Add a second Table View Controller. and cities and the arrays to store the records, we define two more
Connect the first scene to the second scene with a Show segue. Give properties: the database property to store a reference to the CloudKit's
the segue the identifier "showCities". Add a bar button of type Add database and the selectedCountry property to know which country was
to both scenes. Assign the title Countries to the first scene and Cities selected by the user from the initial scene. The database property is
to the second scene, as shown in Figure 17-18. initialized in the init() method with a reference to the Private Database (we
use the private database because we only want the user to be able to
Although this time we are downloading the information from the CloudKit share the data between his or her own devices).
In this example, we get a reference to the CloudKit container with the
servers, we still need a model to temporarily store the records, manage the
default() method, but if the name of the container is different from the app's
bundle, we need to implement the CKContainer initializer with the right

identifier, as in let container = CKContainer (identifier: }


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
"iCloud.com.mydomain.containername").
if segue.identifier == "showCities" {
Each Table View Controller defines its own diffable data source and if let path = self.tableView.indexPathForSelectedRow {
provides the code to download the records from CloudKit servers and add if let record = AppData.dataSourceCountries.itemIdentifier(for: path) {
AppData.selectedCountry = record
more. The following is the view controller we need for the countries. We }
call it CountriesViewController. }
}
}
Listing 17-14: Reading and storing countries in the Private database func prepareDataSource() {
 AppData.dataSourceCountries = UITableViewDiffableDataSource<Sections, CKRecord.ID>
(tableView: tableView) { tableView, indexPath, recordID in
import UIKit let cell = tableView.dequeueReusableCell(withIdentifier: "countriesCell", for: indexPath)
import CloudKit if let item = AppData.listCountries.first(where: { $0.recordID == recordID }) {
var config = cell.defaultContentConfiguration()
class CountriesViewController: UITableViewController { config.text = item["name"] as? String
override func viewDidLoad() { cell.contentConfiguration = config
super.viewDidLoad() }
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "countriesCell") return cell
}
Task(priority: .high) { }
await readCountries() func prepareSnapshot() {
} var snapshot = NSDiffableDataSourceSnapshot<Sections, CKRecord.ID>()
prepareDataSource() snapshot.appendSections([.main])
} snapshot.appendItems(AppData.listCountries.map({ $0.recordID }))
func readCountries() async { AppData.dataSourceCountries.apply(snapshot)
let predicate = NSPredicate(format: "TRUEPREDICATE") }
let query = CKQuery(recordType: "Countries", predicate: predicate) @IBAction func addCountry(_ sender: UIBarButtonItem) {
do { let alert = UIAlertController(title: "Insert Country", message: nil, preferredStyle: .alert)
let list = try await AppData.database.records(matching: query, inZoneWith: nil, let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
desiredKeys: nil, resultsLimit: 0) alert.addAction(cancel)
AppData.listCountries = [] let action = UIAlertAction(title: "Save", style: .default, handler: { (action) in
for (_, result) in list.matchResults { if let fields = alert.textFields, let name = fields.first?.text {
if let record = try? result.get() { Task(priority: .high) {
AppData.listCountries.append(record) await self.insertCountry(name: name)
} }
} }
await MainActor.run { })
prepareSnapshot() alert.addAction(action)
} alert.addTextField(configurationHandler: nil)
} catch { present(alert, animated: true, completion: nil)
print("Error: \(error)") }
} func insertCountry(name: String) async {
} let text = name.trimmingCharacters(in: .whitespaces)
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if !text.isEmpty {
performSegue(withIdentifier: "showCities", sender: self) let id = CKRecord.ID(recordName: "idcountry-\(UUID())")
let record = CKRecord(recordType: "Countries", recordID: id) generates an operation that communicates with the servers
record.setObject(text as NSString, forKey: "name")
asynchronously. If there is no error, we add the record to the listCountries
do { property and update the snapshot (the record is added to the array and
try await AppData.database.save(record)
AppData.listCountries.append(record)
displayed on the Table View only if it is successfully added to the CloudKit
database).
await MainActor.run { The rest of the code is the same used to configure Table Views before.
prepareSnapshot()
} Notice that the diffable data source and the snapshot work with the record
} catch { identifiers. To read this value from the CKRecord objects we get from the
print("Error: \(error)")
}
server, we use the recordID property provided by the CKRecord class.
} The CountriesViewController class includes the tableView(UITableView,
} didSelectRowAt:) method to perform the "showCities" segue when a country
}
 is selected by the user. We have also implemented the prepare() method to
assign the record identifier of the selected country to the selectedCountry
This is a long view controller, but it is easy to understand if we read it part property in the model, so the view controller of the second scene can show
by part. We begin by creating a task that calls the readCountries() method to the list of cities that belong to that country. We called this view controller
get the countries already stored in the CloudKit database. In this method, CitiesViewController.
we define a predicate with the TRUEPREDICATE keyword and a query for
records of type Countries. The type tells the server to only look for Listing 17-15: Storing cities in the Private database
countries, and the TRUEPREDICATE keyword determines that the predicate 
will always return the value true, so we get back all the records available. If import UIKit
the query doesn't return any errors, we get the records from the import CloudKit

matchResults value, add them to the listCountries array, and update the class CitiesViewController: UITableViewController {
snapshot. override func viewDidLoad() {
super.viewDidLoad()
To let the user add a new country, we define an Action for the bar button tableView.register(UITableViewCell.self, forCellReuseIdentifier: "citiesCell")
called addCountry(). When the user presses the button, we create an Alert
Task(priority: .high) {
View with a Text Field inside. After the user inserts a name and presses the
await readCities()
Save button, we call the insertCountry() method in the model with this value }
to add the record to the database. This method defines a record identifier prepareDataSource()
}
with the string "idcountry" followed by a random value generated by the func readCities() async {
UUID() function (the identifiers must be unique). With this identifier, we if AppData.selectedCountry != nil {
let predicate = NSPredicate(format: "country = %@", AppData.selectedCountry)
create a CKRecord object of type Countries, add a property called name let query = CKQuery(recordType: "Cities", predicate: predicate)
with the value received by the method, and finally save it in CloudKit
servers with the save() method of the CKDatabase object. This method do {

let list = try await AppData.database.records(matching: query, inZoneWith: nil, desiredKeys: present(alert, animated: true, completion: nil)
nil, resultsLimit: 0) }
AppData.listCities = [] func insertCity(name: String) async {
for (_, result) in list.matchResults { let text = name.trimmingCharacters(in: .whitespaces)
if let record = try? result.get() { if !text.isEmpty {
AppData.listCities.append(record) let id = CKRecord.ID(recordName: "idcity-\(UUID())")
} let record = CKRecord(recordType: "Cities", recordID: id)
} record.setObject(text as NSString, forKey: "name")
await MainActor.run { let reference = CKRecord.Reference(recordID: AppData.selectedCountry, action:
prepareSnapshot() .deleteSelf)
} record.setObject(reference, forKey: "country")
} catch {
print("Error: \(error)") do {
} try await AppData.database.save(record)
} AppData.listCities.append(record)
}
func prepareDataSource() { await MainActor.run {
AppData.dataSourceCities = UITableViewDiffableDataSource<Sections, CKRecord.ID> prepareSnapshot()
(tableView: tableView) { tableView, indexPath, recordID in }
let cell = tableView.dequeueReusableCell(withIdentifier: "citiesCell", for: indexPath) } catch {
if let item = AppData.listCities.first(where: { $0.recordID == recordID }) { print("Error: \(error)")
var config = cell.defaultContentConfiguration() }
config.text = item["name"] as? String }
cell.contentConfiguration = config }
} }
return cell 
}
}
func prepareSnapshot() { This is also a long view controller, but it performs the same tasks as the
var snapshot = NSDiffableDataSourceSnapshot<Sections, CKRecord.ID>() view controller for the countries. The only difference is that instead of
snapshot.appendSections([.main])
snapshot.appendItems(AppData.listCities.map({ $0.recordID }))
getting all the cities stored in the database, we only get the cities that
AppData.dataSourceCities.apply(snapshot) belong to the country selected by the user. For this purpose, the predicate
} gets only the cities with a country key equal to the value of the
@IBAction func addCity(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "Insert City", message: nil, preferredStyle: .alert) selectedCountry property.
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) The value of this property is also required when the user inserts a new city.
alert.addAction(cancel)
let action = UIAlertAction(title: "Save", style: .default, handler: { (action) in The record is created as before, but this time we create a Reference object
if let fields = alert.textFields, let name = fields.first?.text { with the country's identifier and an action of type deleteSelf, so when the
Task(priority: .high) {
record of the country is deleted, this record is deleted as well.
await self.insertCity(name: name)
}
} Do It Yourself: Create a Swift file called ApplicationData.swift for
})
alert.addAction(action) the code in Listing 17-13. Create a subclass of UITableViewController
alert.addTextField(configurationHandler: nil)
called CountriesViewController, assign it to the initial scene, and
complete it with the code in Listing 17-14. Connect the bar button of
this scene with the Action addCountry(). Create another subclass of
UITableViewController called CitiesViewController, assign it to the second
scene, and complete it with the code in Listing 17-15. Connect the
bar button of this scene with the Action addCity(). Run the application
and press the bar button to insert a country.

The application is ready. When the user inserts a new value, the code
creates a record and uploads it to CloudKit servers, but if we stop and start To add an index, we must press the Add Basic Index button at the bottom
the application again, the countries are not shown on the screen anymore. of the list. The panel opens a form to configure a new index.
This is because we haven't defined the required indexes. CloudKit
automatically creates indexes for every key we include in the records, Figure 17-20: Index configuration
except for the record’s identifier. Therefore, when we query the Cities
records by their country attribute to get the cities that belong to the
selected country, CloudKit knows how to find and return those records, but
when we try to retrieve the Countries records without a predicate,
CloudKit tries to fetch them by the record identifiers and fails because
there is no index associated to that attribute (called recordName). To

create an index for this attribute, we need to go to the dashboard, click on
the Indexes option in the Schema section, and click on the Countries record
There are three types of indexes: Queryable (it can be included in a query),
type to modify it.
Searchable (it can be searched), and Sortable (it can be sorted). By default,
When we click on a record, the panel shows the list of indexes available. By all these indexes are associated to custom attributes but not to the record's
default, the Countries records contains three indexes for the name identifier. When we query the database from the readCountries() method in
attribute, but no index for the record's identifier. the model, we do not specify any field in the predicate and therefore the
system fetches the records by their identifiers, which is described in the
Figure 17-19: Record's indexes database as recordID (recordName). For this reason, to retrieve the
countries in our example, we must add a Queryable index to the recordID
field of the Countries record type, as shown in figure 17-20.
Once we select the recordID field and the Queryable index, we can press
the Save button to save the changes. Now, if we run the application again
from Xcode, the records added to the database are shown on the table.

Assets

Records may include files, and the files may contain anything from pictures
to sound or even videos. To add a file to a record, we must create an asset
with the CKAsset class. The class includes the following initializer.

CKAsset(fileURL: URL)—This initializer creates a CKAsset object with


the content of the file in the location determined by the fileURL
argument.

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 Cities records and add a
scene to show the picture when a city is selected.

Figure 17-21: Interface to store and show assets

Do It Yourself: Add a new scene to the Storyboard. Connect the


second scene to the new scene with a Show segue and give the
segue the identifier "showPicture". Add an Image View to the new
scene, as shown in Figure 17-21.
The following are the changes we have to introduce to the insertCity() IMPORTANT: In this example, we load an image from the bundle. If
method in the CitiesViewController class to get the URL of the image and you want to use an image from the Assets Catalog, you must
assign the asset to the record. temporarily store it in a file and then use the file's URL to create the
asset. For an example, see the code in Listing 16-13. We will study
Listing 17-16: Storing assets how to get images from the Photo Library and take photos from the
 camera in Chapter 18.
func insertCity(name: String) async {
let text = name.trimmingCharacters(in: .whitespaces)
To get the asset back, we have to implement the prepare() method in the
if !text.isEmpty {
let id = CKRecord.ID(recordName: "idcity-\(UUID())") CitiesViewController class to pass the record identifier of the selected city to
let record = CKRecord(recordType: "Cities", recordID: id) the view controller in charge of showing the image to the user.
record.setObject(text as NSString, forKey: "name")
let reference = CKRecord.Reference(recordID: AppData.selectedCountry, action: .deleteSelf)
record.setObject(reference, forKey: "country") Listing 17-17: Passing the selected city to the view controller

let bundle = Bundle.main
if let fileURL = bundle.url(forResource: "Toronto", withExtension: "jpg") { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let asset = CKAsset(fileURL: fileURL) performSegue(withIdentifier: "showPicture", sender: self)
record.setObject(asset, forKey: "picture") }
} override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
do { if segue.identifier == "showPicture" {
try await AppData.database.save(record) if let path = self.tableView.indexPathForSelectedRow {
AppData.listCities.append(record) if let item = AppData.dataSourceCities.itemIdentifier(for: path) {
await MainActor.run { let controller = segue.destination as! PicturesViewController
prepareSnapshot() controller.selectedCity = item
} }
} catch { }
print("Error: \(error)") }
} }
} 
}

Because the values stored in the listCities array are references to the records
of every city, to show the image on the screen we just need to read the
In addition to the previous information, the insertCity() method now gets the
asset from the record, create an image with it, and assign it to the Image
URL of an image included in the project called Toronto.jpg, creates a
View. All this process is done by the view controller of the scene added in
CKAsset object with it, and assigns the object to the record of every city
Figure 17-21. We call this view controller PicturesViewController.
with the "picture" key. Now, besides the name, a file with this image will be
stored for every city inserted by the user.
Listing 17-18: Reading assets

import UIKit

import CloudKit Subscriptions


class PicturesViewController: UIViewController { 
@IBOutlet weak var cityPicture: UIImageView!
var selectedCity: CKRecord.ID!
The previous example fetches the records available and shows them on the
override func viewDidLoad() {
if let item = AppData.listCities.first(where: { $0.recordID == selectedCity }) { screen every time the scene is loaded. This means that records added to
if let asset = item["picture"] as? CKAsset, let url = asset.fileURL { the database from another device will not be visible until the scene is
self.cityPicture.image = UIImage(contentsOfFile: url.path)
}
loaded again. This is not the behavior expected by the users. When
} working with applications that store information online, users expect the
} information to be updated as soon as it becomes available. To provide this
}
 feature, CloudKit implements subscriptions.
Subscriptions are queries stored by our application in CloudKit servers.
How to read the asset stored in the record depends on the type of content When a change occurs in the database, the query detects the modification
managed by the asset. In this example, we must use the asset's path to and triggers the delivery of a Remote Notification from the iCloud servers
create a UIImage object and then assign this object to the UIImageView on the to the copy of the app that registered the subscription.
interface. As a result, every time the user selects a city, the asset is turned Database subscriptions are created from the CKDatabaseSubscription class (a
into an image and shown on the screen. subclass of a more generic class called CKSubscription). The class includes the
following initializer.
Do It Yourself: Update the CitiesViewController class with the methods
in Listings 17-16 and 17-17. Download the Toronto.jpg picture from CKDatabaseSubscription(subscriptionID: String)—This initializer
our website and add the file to your project. Create a subclass of the creates a CKDatabaseSubscription object that represents a subscription
UIViewController class called PicturesViewController and assign it to the last with the identifier specified by the subscriptionID argument.
scene. Complete the class with the code in Listing 17-18. Connect
the Image View with the Outlet called cityPicture. Run the application, Subscriptions are also added to CloudKit servers with an operation. The
CKDatabase class offers convenient methods to create these operations.
select a country, and add a new city. Select the city. You should see
the Toronto.jpg image on the screen.
save(CKSubscription)—This asynchronous method stores in the
servers the subscription specified by the argument.
deleteSubscription(withID: String)—This asynchronous method
removes from the server the subscription with the identifier specified
by the withID argument.
After our app registers a subscription in the server, we must listen to registration is successful, the system calls the delegate method introduced
Remote Notifications and download the changes. The first thing our above every time a notification is received, so we need to implement that
application needs to do to be able to receive these notifications is to method as well, as shown next.
register with the iCloud servers. The UIApplication class offers the following
method for this purpose. Listing 17-19: Processing Remote Notifications

registerForRemoteNotifications()—This method registers the app import UIKit
import CloudKit
in the iCloud servers to receive Remote Notifications. A token is
generated to identify each copy of our app, so the notifications are @main
class AppDelegate: UIResponder, UIApplicationDelegate {
delivered to the right user. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let userSettings = UserDefaults.standard
To report to our application that a Remote Notification was received, the let values = ["subscriptionSaved": false, "zoneCreated": false]
UIApplication object calls a method in the app's delegate. The following is the userSettings.register(defaults: values)
method defined by the UIApplicationDelegate class for this purpose.
application.registerForRemoteNotifications()
Task(priority: .background) {
application(UIApplication, didReceiveRemoteNotification: await AppData.configureDatabase()
}
Dictionary, fetchCompletionHandler: Block)—This method is return true
called by the application on its delegate when a Remote Notification is }
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo:
received. The didReceiveRemoteNotification argument is a dictionary [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping
with information about the notification, and the (UIBackgroundFetchResult) -> Void) {
let notification = CKNotification(fromRemoteNotificationDictionary: userInfo) as?
fetchCompletionHandler argument is a closure that we must execute CKDatabaseNotification
guard notification != nil else {
after all the custom tasks are performed. The closure must be called
completionHandler(.failed)
with a value that describes the result of the operation. For this return
}
purpose, UIKit offers the UIBackgroundFetchResult enumeration with the AppData.checkUpdates(finishClosure: { (result) in
values newData (new data was downloaded), noData (no data was completionHandler(result)
})
downloaded), and failed (the app failed to download the data). }
func application(_ application: UIApplication, configurationForConnecting
connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
Setting up a subscription on CloudKit servers requires us to follow several UISceneConfiguration {
steps. To begin with, we must call the registerForRemoteNotifications() method of return UISceneConfiguration(name: "Default Configuration", sessionRole:
the UIApplication object as soon as the application is launched to tell the connectingSceneSession.role)
}
system that we want to register the application to receive Remote }
Notifications from iCloud servers (Apple's Push Notification service). If the 

launches our application and puts it in the background to allow it to


Subscriptions only report changes in customs zones. Therefore, if we want contact the servers and process the information. But because this process
to receive notifications, besides creating the subscription we also must consumes resources, the system needs to know when the operation is over
create a record zone and store all our records in it. This is the reason why, and therefore it requires us to report it by calling the closure received by
the first thing we do when the application is launched, is to store two the completionHandler parameter with a value of the UIBackgroundFetchResult
Boolean values in the User Defaults database called "subscriptionSaved" enumeration that determines what happened (newData if we downloaded
and "zoneCreated". These values will be used later to check whether we new data, noData if there was nothing to download, and failed if the process
have already created the subscription and the custom zone. After these failed). By calling the closure, we tell the system that the process is over,
values are set, we call the registerForRemoteNotifications() method on the but because the operations are performed asynchronously, we can't do it
UIApplication object to register the application with iCloud servers and an until they are finished. That's the reason why, in the example of Listing 17-
asynchronous method in our model called configureDatabase() that we are 19, we execute a method in the model called checkUpdates() that takes a
going to define later to create the subscription and the custom zone. closure. This method downloads the new information and executes the
The registerForRemoteNotifications() method prepares the application to receive closure when finished. This way, we can call the completionHandler closure
notifications, but the notifications are processed by the delegate method. after all the operations were performed.
The first thing we do in this method is to check whether the notification
received is a notification sent by a CloudKit server. For this purpose, the IMPORTANT: The UIApplicationDelegate class also defines an
CloudKit framework includes the CKNotification class with properties we can asynchronous method to receive remote notifications called
use to process the dictionary received by the method. The class includes application(UIApplication, didReceiveRemoteNotification: Dictionary). In this
the following initializer. example, we are using concurrent operations, but if you need to
perform asynchronous operations, you may implement this method
CKNotification(fromRemoteNotificationDictionary: instead.
Dictionary)—This initializer creates a CKNotification object from the
information included in the notification (the value of the userInfo The next step is to implement the methods that are going to contact the
parameter in the delegate method). CloudKit servers and process the information. In the application's delegate,
we called two methods of our ApplicationData class: the configureDatabase()
Because CloudKit servers can send other types of notifications, we also method to create the subscription and the zone, and the checkUpdates()
check whether the notification received is of type CKDatabaseNotification. In method to download and process the information. The following is our
case of success, we proceed to download the data. implementation of the configureDatabase() method. (This example assumes
Before going through the process of downloading the new information that we are working with the model introduced in Listing 17-13.)
from CloudKit servers, we must consider that the system requires us to
report the result of the operation. Notifications may be received when the Listing 17-20: Configuring the database
application is closed or in the background. If this happens, the system 
func configureDatabase() async { The next step is to create the custom zone. Again, we check the Boolean
let userSettings = UserDefaults.standard
if !userSettings.bool(forKey: "subscriptionSaved") { value stored in User Defaults to know if there is a custom zone already on
let newSubscription = CKDatabaseSubscription(subscriptionID: "updatesDatabase") the server, and if not, we create one called listPlaces to store our records. If
let info = CKSubscription.NotificationInfo()
the operation is successful, we assign the value true to the zoneCreated
info.shouldSendContentAvailable = true
newSubscription.notificationInfo = info value, so the app knows that the zone was already created.
Next, we must define the checkUpdates() method to download and process
do {
try await database.save(newSubscription) the changes in the database. But first, we need to think about how we are
userSettings.set(true, forKey: "subscriptionSaved") going to organize our code. Every process executed in CloudKit servers is
} catch {
print("Error: \(error)")
performed by asynchronous operations. This means that we need to think
} about the order in which the operations are executed. For some, the order
} doesn't matter, but for others it is crucial. For instance, we cannot store
if !userSettings.bool(forKey: "zoneCreated") {
let newZone = CKRecordZone(zoneName: "listPlaces") records in a zone before the zone is created. As we have seen in Chapter
do { 14, we can use Swift concurrency to control the order in which operations
try await database.save(newZone)
userSettings.set(true, forKey: "zoneCreated") are performed, but CloudKit operations are already concurrent. Therefore,
} catch { depending on the requirements of our application, it may be better to
print("Error: \(error)")
}
implement closures to execute code only after an operation is over, and
} this is the approach we take in this example. 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 want to execute once the
The first thing we do in the configureDatabase() method is to check the operation is over. This way, we make sure that the operations are over
subscriptionSaved value in the User Defaults to know if the subscription before doing anything else.
was already created. If not, we use the CKDatabaseSubscription initializer to
create a subscription with the name updatesDatabase and then define the Listing 17-21: Initiating the process to get the updates from the server

notificationInfo property to configure the notifications that are going to be
func checkUpdates(finishClosure: @escaping (UIBackgroundFetchResult) -> Void) {
sent by the server. For this purpose, the framework defines the Task(priority: .background) {
CKNotificationInfo class. This class includes multiple properties to configure await configureDatabase()
Remote Notifications for CloudKit, but database subscriptions only require downloadUpdates(finishClosure: finishClosure)
}
us to set the shouldSendContentAvailable property to true. After this, the }
subscription is saved on the server with the save() method of the CKDatabase 
object and the subscriptionSaved value is changed to true if the operation is
successful. The checkUpdates() method calls the configureDatabase() method again to make
sure that the database is configured properly.

To simplify the code, we moved the statements to an additional method receives a value of type CKRecordZone.ID with the identifier of the zone
called downloadUpdates(). So after we confirm that the zone was created, we that changed.
call this method with a reference to the closure received by the
changeTokenUpdatedBlock—This property sets a closure that is
checkUpdates() method. (We pass the closure from one method to another
executed to provide the last database token. The closure receives an
and execute it after all the operations are over, as we will see next.)
object of type CKServerChangeToken with the current token that we can
IMPORTANT: Passing closures from one method to another is a store to send to subsequent operations.
way to control the order in which the code is executed when we use fetchDatabaseChangesResultBlock—This property sets a closure
concurrent operations. We chose this programming pattern for this that is executed when the operation is over. The closure receives a
example because it simplifies the code, but as we mentioned before, Result enumeration to report the success or failure of the operation.
in some cases may be better to implement Swift concurrency. For The enumeration value includes a tuple and a CKError value to report
more information, see Chapter 14. 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 this operation, we must perform another
available in the database is created from a subclass of the
operation to download the changes. For this purpose, the framework
CKDatabaseOperation class called CKFetchDatabaseChangesOperation. This class
includes the CKFetchRecordZoneChangesOperation class with the following
includes the following initializer.
initializer.

CKFetchDatabaseChangesOperation(previousServerChangeT
CKFetchRecordZoneChangesOperation(recordZoneIDs:
oken: CKServerChangeToken?)—This initializer creates an
[CKRecordZone.ID], configurationsByRecordZoneID:
operation to fetch changes from a database. The argument is a token
Dictionary)—This initializer creates an operation to download
that determines which changes were already fetched. If we specify a
changes from a database. The recordZoneIDs argument is an array
token, only the changes that occurred after the token was created are
with the IDs of all the zones that present changes, and the
fetched.
configurationsByRecordZoneID argument is a dictionary with
configuration values for each zone. The dictionary takes
The class also includes properties to define completion handlers for every
CKRecordZone.ID objects as keys and options determined by an object
step of the process.
of the ZoneConfiguration class included in the
CKFetchRecordZoneChangesOperation class. The class includes three
recordZoneWithIDChangedBlock—This property sets a closure
that is executed to report which zones present changes. The closure properties to define the options: desiredKeys (array of strings with the
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.
fetchRecordZoneChangesResultBlock—This property sets a
The CKFetchRecordZoneChangesOperation class also includes properties to define closure that is executed after the operation is over. The closure
completion handlers for every step of the process.
receives a Result enumeration value to report errors.

recordWasChangedBlock—This property sets a closure that is CloudKit servers use tokens to know which changes were already sent to
executed when a new or updated record is downloaded. The closure every copy of the app, so the information is not downloaded twice from
receives two values: a CKRecord.ID with the identifier of the record the same device. If a device stores or modifies a record, the server
that changed, and a Result enumeration value to report the success or generates a new token, so next time a device accesses the servers only the
failure of the operation. The enumeration includes two values: a changes introduced after the last token will be downloaded, as shown in
CKRecord object with the record that changed and a CKError value to Figure 17-22.
report errors.
recordWithIDWasDeletedBlock—This property sets a closure that Figure 17-22: Tokens
is executed when the operation finds a deleted record. The closure
receives two values: a CKRecord.ID object with the identifier of the
record that was deleted, and a string with the record's type.
recordZoneChangeTokensUpdatedBlock—This property sets a
closure that is executed when the change token for the zone is
updated. The closure receives three values: a CKRecordZone.ID with the
identifier of the zone associated to the token, a CKServerChangeToken 
object with the current token, and a Data structure with the last token
sent by the app to the server. In the process depicted in Figure 17-22, the app in Device 1 stores a new
record in the server (Record 1). To report the changes, the server generates
recordZoneFetchResultBlock—This property sets a closure that is a new token (A). When the app in Device 2 connects to the server, the
executed when the operation finishes downloading the changes of a server detects that this device does not have the latest token, so it returns
zone. The closure receives two values: a CKRecordZone.ID with the Record 1 and the current token (A) to update the state in this device. If
zone's identifier, and a Result enumeration value to report the success later the user decides to create a new record from Device 2 (Record 2), a
or failure of the operation. The enumeration includes two values: a new token will be created (B). The next time Device 1 connects to the
tuple and a CKError value to report errors. In turn, the tuple includes

server, it will find that its token is different from the server's token, so it changeToken = token
}
will download the modifications inserted after token A. operation.fetchDatabaseChangesResultBlock = { result in
Tokens are great because they allow us to only get the latest changes, but guard let values = try? result.get() else {
finishClosure(UIBackgroundFetchResult.failed)
this process is not automatic, we are responsible of storing the current
return
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, if zonesIDs.isEmpty {
finishClosure(UIBackgroundFetchResult.noData)
we need two tokens: one to keep track of the changes in the database and } else {
another for the custom zone created by the configureDatabase() method. To changeToken = values.serverChangeToken
work with these values, we are going to use two variables changeToken for let configuration = CKFetchRecordZoneChangesOperation.ZoneConfiguration()
the database token and fetchChangeToken for the token of our custom zone, configuration.previousServerChangeToken = changeZoneToken
and we are going to store them permanently in User Settings for future let fetchOperation = CKFetchRecordZoneChangesOperation(recordZoneIDs: zonesIDs,
configurationsByRecordZoneID: [zonesIDs[0]: configuration])
reference. All this process is performed by the downloadUpdates() method, as
shown next. fetchOperation.recordWasChangedBlock = { recordID, result in
guard let record = try? result.get() else {
print("Error")
Listing 17-22: Getting the updates from the server return
}

if record.recordType == "Countries" {
func downloadUpdates(finishClosure: @escaping (UIBackgroundFetchResult) -> Void) { let index = self.listCountries.firstIndex(where: { item in
var changeToken: CKServerChangeToken! return item.recordID == recordID
var changeZoneToken: CKServerChangeToken! })
if index != nil {
let userSettings = UserDefaults.standard self.listCountries[index!] = record
if let data = userSettings.value(forKey: "changeToken") as? Data { } else {
if let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, self.listCountries.append(record)
from: data) { }
changeToken = token } else if record.recordType == "Cities" {
} if let country = record["country"] as? CKRecord.Reference {
} if country.recordID == self.selectedCountry {
if let data = userSettings.value(forKey: "changeZoneToken") as? Data { let index = self.listCities.firstIndex(where: { item in
if let token = try? NSKeyedUnarchiver.unarchivedObject(ofClass: CKServerChangeToken.self, return item.recordID == record.recordID
from: data) { })
changeZoneToken = token if index != nil {
} self.listCities[index!] = record
} } else {
var zonesIDs: [CKRecordZone.ID] = [] self.listCities.append(record)
let operation = CKFetchDatabaseChangesOperation(previousServerChangeToken: }
changeToken) }
operation.recordZoneWithIDChangedBlock = { zoneID in }
zonesIDs.append(zoneID) }
} }
operation.changeTokenUpdatedBlock = { token in fetchOperation.recordWithIDWasDeletedBlock = { recordID, recordType in
if recordType == "Countries" { await self.updateInterface()
let index = self.listCountries.firstIndex(where: { item in }
return item.recordID == recordID finishClosure(UIBackgroundFetchResult.newData)
}) }
if index != nil { self.database.add(fetchOperation)
self.listCountries.remove(at: index!) }
} }
} else if recordType == "Cities" { database.add(operation)
let index = self.listCities.firstIndex(where: { item in }
return item.recordID == recordID func updateInterface() async {
}) await MainActor.run {
if index != nil { var snapshotCountries = NSDiffableDataSourceSnapshot<Sections, CKRecord.ID>()
self.listCities.remove(at: index!) snapshotCountries.appendSections([.main])
} snapshotCountries.appendItems(listCountries.map({ $0.recordID }))
} dataSourceCountries?.apply(snapshotCountries)
}
fetchOperation.recordZoneChangeTokensUpdatedBlock = { zoneID, token, data in var snapshotCities = NSDiffableDataSourceSnapshot<Sections, CKRecord.ID>()
changeZoneToken = token snapshotCities.appendSections([.main])
} snapshotCities.appendItems(listCities.map({ $0.recordID }))
fetchOperation.recordZoneFetchResultBlock = { zoneID, result in dataSourceCities?.apply(snapshotCities)
guard let values = try? result.get() else { }
print("Error") }
return 
}
changeZoneToken = values.serverChangeToken
} This is a very long method that we need to study piece by piece. As
fetchOperation.fetchRecordZoneChangesResultBlock = { result in mentioned before, we start by defining the properties we are going to use
switch result {
case .failure(_):
to store the tokens (one for the database and another for the custom
finishClosure(UIBackgroundFetchResult.failed) zone). Next, we check if there are tokens already stored in the User
return Defaults database. Because the tokens are instances of the
default:
break CKServerChangeToken class, we can't store their values directly in User
} Defaults, we must first convert them into Data structures. This is the reason
if changeToken != nil {
if let data = try? NSKeyedArchiver.archivedData(withRootObject: changeToken!, why, when we read the values, we cast them as Data with the as? operator
requiringSecureCoding: false) { and then unarchive them with the unarchivedObject() method of the
userSettings.set(data, forKey: "changeToken")
NSKeyedUnarchiver class (see Chapter 15).
}
} Next, we configure the operations necessary to get the updates from the
if changeZoneToken != nil { server. We must perform two operations on the database, one to
if let data = try? NSKeyedArchiver.archivedData(withRootObject: changeZoneToken!,
requiringSecureCoding: false) { download the list of changes available and another to download the actual
userSettings.set(data, forKey: "changeZoneToken") changes and show them to the user. The operations are performed and
}
}
then the results are reported to the closures assigned to their properties.
Task(priority: .background) {

The first operation we need to perform is the CKFetchDatabaseChangesOperation dictionary with the zone identifiers as keys and ZoneConfiguration objects that
operation. The initializer requires the previous token to get only the include the previous token for each zone as values. Because in this example
changes that are not available on the device, so we pass the value of the we only work with one zone, we read the first element of the zonesIDs array
changeToken property. Next, we define the closures for each of its properties. to get the identifier of our custom zone and provide a ZoneConfiguration
This operation includes three properties, one to report the zones that object with the current token for the zone stored in the changeZoneToken
changed, one to report the creation of a new database token, and another property.
to report the conclusion of the operation. The first property defined in our This operation works like the previous one. The changes are fetched, and
example is recordZoneWithIDChangedBlock. The closure assigned to this the results are reported to the closures assigned to its properties. The first
property is executed every time the system finds a zone whose content has property declared in Listing 17-22 is recordWasChangedBlock. The closure
changed. In this closure, we add the zone ID to an array to keep a reference assigned to this property is called every time a new or updated record is
of each zone that changed. received. Here, we check if the record is of type Countries or Cities and
Something similar happens with the closure assigned next to the store it in the corresponding array. When the record is of type Countries,
changeTokenUpdatedBlock property. This closure is executed every time the we use the firstIndex(where:) method to look for duplicates. If the record
system decides to perform the operation again to download the changes in already exists in the array, we update its values, otherwise, we add the
separate processes. To make sure that we only receive the changes that we record to the list. We do something similar for the Cities records, except
did not process yet, we use this closure to update the changeToken property this time we first check whether the record contains a reference to a
with the current token. country and only update or add the record to the array if the reference
The last property we have defined for this operation is corresponds to the country currently selected by the user (the listCities array
fetchDatabaseChangesResultBlock. The closure assigned to this property is only contains the cities of the selected country).
executed to let the app know that the operation is over, and this is how we The closure of the recordWithIDWasDeletedBlock property defined next is
know that we have all the information we need to begin downloading the executed every time the app receives the ID of a deleted record (a record
changes with the second operation. This closure receives a Result that was deleted from the CloudKit database). In this case, we do the same
enumeration value, which includes a tuple with two values and an Error as before but instead of updating or adding the record we remove it from
value to report errors. If no values are returned, we execute the finishClosure the list with the remove() method.
closure with the value failed and the operation is over. On the other hand, if The closures of the next two properties, recordZoneChangeTokensUpdatedBlock
there are values available, we check if the zoneIDs array contains any zone and recordZoneFetchResultBlock, are executed when the process completes a
ID. If it is empty, it means that there are no changes available and therefore cycle, either because the system decides to download the data in multiple
we execute the finishClosure closure with the value noData, but if the array is processes, or the operation finished fetching the changes in a zone.
not empty, we store the last token in the changeToken variable and configure Depending on the characteristics of our application, we may need to
the CKFetchRecordZoneChangesOperation operation to download the changes. perform some tasks in these closures, but in our example, we just store the
The CKFetchRecordZoneChangesOperation operation is performed over the zones current token in the changeZoneToken property so the next time the operation
that changed, so we must initialize it with the array of zone identifiers is performed we only get the changes we have not downloaded yet.
generated by the previous operation. The initializer also requires a
Finally, the closure assigned to the fetchRecordZoneChangesResultBlock property } catch {
print("Error: \(error)")
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, 
we store the current tokens in the User Defaults database, call the
updateInterface() method to update the interface, and finally call the All we need to do to store a record in a custom zone is to create the
finishClosure closure with the value newData, to tell the system that new data CKRecordZone object and use the zone identifier to create the CKRecord.ID
has been downloaded. Notice that to store the tokens we must turn them object, as we did in this example.
into Data structures and encode them with the archivedData() method of the The following are the same changes applied to the insertCity() method of the
NSKeyedArchiver class (see Chapter 15). CitiesViewController class to store the Cities records in the custom zone.
Lastly, after the definition of each operation and their properties, we call
the add() method of the CKDatabase object to add them to the database. Listing 17-24: Storing the Cities records in a custom zone
There is one more change we must perform for the subscription to work. 
So far, we have stored the records in the zone by default, but as we already func insertCity(name: String) async {
await AppData.configureDatabase()
mentioned, subscriptions require the records to be stored in a custom
zone. The following are the changes we must introduce to the let text = name.trimmingCharacters(in: .whitespaces)
if !text.isEmpty {
insertCountry()method of the CountriesViewController class to store the records
let zone = CKRecordZone(zoneName: "listPlaces")
inside the listPlaces zone created before. let id = CKRecord.ID(recordName: "idcity-\(UUID())", zoneID: zone.zoneID)
let record = CKRecord(recordType: "Cities", recordID: id)
record.setObject(text as NSString, forKey: "name")
Listing 17-23: Storing the Countries records in a custom zone let reference = CKRecord.Reference(recordID: AppData.selectedCountry, action: .deleteSelf)
 record.setObject(reference, forKey: "country")
func insertCountry(name: String) async {
let bundle = Bundle.main
await AppData.configureDatabase()
if let fileURL = bundle.url(forResource: "Toronto", withExtension: "jpg") {
let asset = CKAsset(fileURL: fileURL)
let text = name.trimmingCharacters(in: .whitespaces)
record.setObject(asset, forKey: "picture")
if !text.isEmpty {
}
let zone = CKRecordZone(zoneName: "listPlaces")
do {
let id = CKRecord.ID(recordName: "idcountry-\(UUID())", zoneID: zone.zoneID)
try await AppData.database.save(record)
let record = CKRecord(recordType: "Countries", recordID: id)
AppData.listCities.append(record)
record.setObject(text as NSString, forKey: "name")
await MainActor.run {
do {
prepareSnapshot()
try await AppData.database.save(record)
}
AppData.listCountries.append(record)
} catch {
print("Error: \(error)")
await MainActor.run {
}
prepareSnapshot()
}
}

} Errors


Notice that the first thing we do in both methods is to call the
configureDatabase() method. We call this method again, so every time a record Errors are an important part of CloudKit. The service is highly dependable
is inserted, we check that the subscription and the zone were already on the network and how reliable it is. If the device is disconnected or the
added to the database. connection is not good enough, the operations are not performed or may
be lost. CloudKit does not provide a standard solution for these situations,
Do It Yourself: Update the AppDelegate class with the code in Listing it just returns an error and expects our app to solve the problem. If the
17-19. Add the methods in Listings 17-20, 17-21, and 17-22 to the user creates a new record but at that moment the device is disconnected
ApplicationData class. Update the insertCountry() method of the from the Internet, our app is responsible for registering the incident and
CountriesViewController class with the code in Listing 17-23 and the trying again later.
insertCity() method of the CitiesViewController class with the code in The most common error is related to the user's iCloud account. Every user
Listing 17-24. Run the application in two different devices and insert must have an iCloud account to access CloudKit servers. If an iCloud
account is not set on the device or has restrictions due to Parental Control
a new country. You should see the country appear on the screen of
or Device Management, the app will not be able to connect to the servers.
the second device.
The CKContainer class offers the following method to check the status of the
user's account.
IMPORTANT: Remote Notifications can only be tested on a real
device (they do not work on the simulator). If you only have one accountStatus()—This asynchronous method attempts to access the
device, you can test your applications by adding records from the user's iCloud account and returns a CKAccountStatus enumeration to
CloudKit dashboard.
report the current state. The enumeration includes the values
couldNotDetermine, available, restricted, and noAccount.

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.

CKAccountChanged—This notification is posted by the system


when the status of the user's iCloud account registered on the device
changes.
We should always check if the servers are available before trying to This example checks the status of the account and prints a message on the
perform an operation and warn the user about it. For instance, we can console if an error occurs or the status is other than available. If an error
modify the insertCountry() method in our model to check the status of the occurs, the code returns from the function without letting the user insert
connection before introducing a new record. the new record.

Listing 17-25: Checking CloudKit availability Do It Yourself: Update the insertCountry() method in the
 class with the code in Listing 17-25. Run the
CountriesViewController
func insertCountry(name: String) async { application in a device and activate Airplane Mode from Settings.
await AppData.configureDatabase()
do { Add a new country. You should see a CKError on the console that
let container = CKContainer.default() reads "Network Unavailable".
let status = try await container.accountStatus()
if status != CKAccountStatus.available {
print("iCloud Not Available") IMPORTANT: Of course, this example is just for didactic purposes.
return
} The information inserted by the user should be stored locally and
} catch { then uploaded to the servers when the connection becomes
print("Error: \(error)")
return available again. This requires storing the information locally,
} checking for errors, marking every value inserted by the user as
let text = name.trimmingCharacters(in: .whitespaces)
if !text.isEmpty { uploaded or not, and constantly trying to upload the values again
let zone = CKRecordZone(zoneName: "listPlaces")
let id = CKRecord.ID(recordName: "idcountry-\(UUID())", zoneID: zone.zoneID)
when the operation failed before, which may involve hundreds of
let record = CKRecord(recordType: "Countries", recordID: id) lines of code. Fortunately, Apple offers a better solution that
record.setObject(text as NSString, forKey: "name")
integrates CloudKit with Core Data and takes care of everything for
do { us. We will learn more about it in the next section of this chapter.
try await AppData.database.save(record)
AppData.listCountries.append(record)
In the last example, we just checked whether an error occurred or not and
await MainActor.run { proceeded accordingly, but we can also identify the type of error returned
prepareSnapshot()
} by the operation. Errors are objects that conform to the Error protocol.
} catch { Every time we want to read an error, we must cast it to the right type. In
print("Error: \(error)")
} CloudKit, the errors are of type CKError, which is a structure initialized from
} an object of type NSError and therefore it inherits the following property to
}

return the error code.

code—This property returns a value that identifies the error found. message "Not Found" on the console (there is no zone called
The property is of type CKError.Code; an enumeration inside the "myNewZone").
CKError structure with values that represent all the errors produced by
CloudKit. The list of values available is extensive. The most frequently
used are partialFailure, networkUnavailable, networkFailure, serviceUnavailable,
unknownItem, operationCancelled, changeTokenExpired, quotaExceeded,
zoneNotFound, and limitExceeded.

The following example implements the recordZone() method of the


CKDatabase object to check whether a zone exists in the database. In the
catch block, we cast the value of the error parameter as a CKError structure
and then compare the value of its code property with the value zoneNotFound
of the Code enumeration. If the values match, it means that the zone we
tried to access does not exist.

Listing 17-26: Checking for errors



func checkZones() async {
let newZone = CKRecordZone(zoneName: "myNewZone")
do {
try await database.recordZone(for: newZone.zoneID)
} catch {
if let error = error as? CKError {
if error.code == CKError.Code.zoneNotFound {
print("Not found")
} else {
print("Zone Found")
}
}
}
}

Do It Yourself: Add the checkZones() method in Listing 17-26 to the


ApplicationDataclass. Call this method from an asynchronous task in
the ApplicationData initializer. Run the application. You should see the
CloudKit and Core Data
record(for: NSManagedObjectID)—This method returns a

CKRecord object with the record that corresponds to the Core Data
Loading the information from CloudKit every time the app is launched is object specified by the for argument. The argument is the object's
not practical or even reliable. The device may get disconnected, the servers identifier (returned by the objectID property). If no record is found, the
may not be always available, or the response may take too long to arrive. method returns nil.
For these reasons, Subscriptions are usually implemented to keep data records(for: [NSManagedObjectID])—This method returns an
updated in a local storage. The application stores the information in a local array of CKRecord objects with the records that correspond to the Core
storage, like a Core Data Persistent Store, and uses CloudKit to share that Data objects specified by the for argument. The argument is an array
data with other devices and keep them synchronized. Using CloudKit in this of identifiers (returned by the objectID property).
manner, we can create an application that automatically uploads and
downloads the data to the servers, so we always display the same recordID(for: NSManagedObjectID)—This method returns a
information to the user no matter where the app is running. CKRecord.ID value with the identifier of the record that corresponds to
As we already mentioned in the previous section of this chapter, the Core Data object specified by the for argument. The argument is
synchronizing a Core Data Persistent Store with a CloudKit database the object's identifier (returned by the objectID property).
requires controlling the information that is uploaded and downloaded recordIDs(for: [NSManagedObjectID])—This method returns an
from the servers, checking for errors, solving conflicts, and making sure array of CKRecord.ID values with the identifiers of the records that
that no value is duplicated or lost. Because this work is pretty much the
correspond to the Core Data objects specified by the for argument.
same for all applications, Apple offers an API that performs all these
The argument is an array of object identifiers (returned by the objectID
standard tasks for us. All we need to do is to create the Core Data stack
property).
with the NSPersistentCloudKitContainer class instead of the NSPersistentContainer
class and the Persistent Store is automatically synchronized with CloudKit
The NSPersistentCloudKitContainer object automatically resolves conflicts for us,
servers. This class is a subclass of the NSPersistentContainer class and therefore
but we must configure the context to determine how those conflicts are
it includes the same initializer.
going to be resolved. The NSManagedObjectContext class includes the following
properties for this purpose.
NSPersistentCloudKitContainer(name: String)—This initializer
creates a Persistent Store with the name specified by the name
automaticallyMergesChangesFromParent—This property sets or
argument.
returns a Boolean value that determines whether the changes in the
Persistent Store and the context are automatically merged.
Besides the common properties, like the viewContext property that returns a
reference to the context, this subclass also includes the following methods mergePolicy—This property sets or returns an object that decides
in case our application needs to retrieve records manually. the policy that the context is going to use to merge the changes in the

Persistent Store and the context. The Core Data framework defines entity to the other (the relationship in the Countries entity must be set to
global variables to set standard policies. The NSErrorMergePolicy variable To-Many because a country can have many cities).
returns an error if the objects are different, the There is one more requirement for the model to be ready to work with
NSMergeByPropertyStoreTrumpMergePolicy variable replaces the changes in CloudKit. We must select the Configuration (Figure 17-24, number 1) and
memory by the external changes, the check the option Used with CloudKit in the Data Model Inspector panel
(Figure 17-24, number 2), so all the objects stored for these entities are
NSMergeByPropertyObjectTrumpMergePolicy replaces the external changes
synchronized with CloudKit.
by the changes in memory, the NSOverwriteMergePolicy variable replaces
the values in the Persistent Store by the current changes, and the
Figure 17-24: Used with CloudKit option
NSRollbackMergePolicy uses the version of the objects in the Persistent
Store.

Because of this amazing API, creating an application that stores


information locally in a Persistent Store and synchronizes that data with a
CloudKit database is extremely simple. All we need to do is to define the 
Core Data stack with the NSPersistentCloudKitContainer class and then create
the Core Data application as we always do. For instance, we can reproduce Do It Yourself: Create a new project. Open the Signing &
the previous example to store countries and cities and all the objects will Capabilities panel, add the iCloud capability, and check the CloudKit
be uploaded to CloudKit servers and shared between devices. The option (see Figure 17-14). Add a container with the name of the
following is the Core Data model we need for this application. app's bundle and press the Refresh button. Add the Background
Modes capability and check the options Background fetch and
Figure 17-23: Core Data Model
Remote Notifications. Create a Core Data model from the File menu.
Add two entities to the model called Countries and Cities. Add an
attribute of type String to both entities called name. Add a To-Many
relationship to the Countries entity called cities and a To-One
relationship to the Cities entity called country. Select the Inverse
value in the relationship (countries for the Cities entity and cities for
the Countries entity). Select the Default configuration (Figure 17-24,

number 1) and check the Used with CloudKit option (Figure 17-24,
number 2).
For this example, we only need two entities, Countries and Cities, with only
one attribute each of type String called name, and a relationship from one
IMPORTANT: If you need to define some Entities only for local
storage, you can create a custom configuration, assign those Entities The persistentContainer property is set as before, but instead of returning an
to the new configuration, and deactivate the option Used with NSPersistentContainer value it returns an NSPersistentCloudKitContainer value to

CloudKit. After this, the objects stored for those Entities will not be synchronize the Persistent Store with CloudKit servers. Another difference
uploaded to CloudKit. with previous examples is that now we must tell the context that we need
to merge the changes and how to do it. For this purpose, we assign the
value true to the automaticallyMergesChangesFromParent property and the value
Once the application is configured to work with CloudKit and the Core Data
NSMergeByPropertyObjectTrumpMergePolicy to the mergePolicy property.
model is ready, we can work on our code. First, we must initialize the Core
And that's all it takes. From now on, all the changes introduced in the
Data stack in the AppDelegate class, as we did in Chapter 15.
Persistent Store are going to be uploaded to CloudKit and every device
running the application is going to be automatically synchronized. For this
Listing 17-27: Preparing Core Data to work with CloudKit
example, we are going to implement the same interface as before, with

two Table View Controllers to list the countries and cities, as shown below.
import UIKit
import CoreData
Figure 17-25: Interface to work with Core Data and CloudKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
lazy var persistentContainer: NSPersistentCloudKitContainer = {
let container = NSPersistentCloudKitContainer(name: "AnotherTest")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true

container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return container In the model, we only need properties to store the diffable data sources
}()
func application(_ application: UIApplication, configurationForConnecting and the Fetched Results Controllers for both Table View Controllers.
connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole:
Listing 17-28: Defining the model to work with CloudKit
connectingSceneSession.role) 
}
import UIKit
}
import CoreData

import CloudKit

AppData.dataSourceCountries = UITableViewDiffableDataSource<Sections,
enum Sections { NSManagedObjectID>(tableView: tableView) { tableView, indexPath, countryID in
case main let cell = tableView.dequeueReusableCell(withIdentifier: "countriesCell", for: indexPath)
} if let country = try? self.context.existingObject(with: countryID) as? Countries {
class ApplicationData { var config = cell.defaultContentConfiguration()
var selectedCountry: Countries! config.text = country.name
cell.contentConfiguration = config
var dataSourceCountries: UITableViewDiffableDataSource<Sections, NSManagedObjectID>! }
var fetchedControllerCountries: NSFetchedResultsController<Countries>! return cell
}
var dataSourceCities: UITableViewDiffableDataSource<Sections, NSManagedObjectID>! }
var fetchedControllerCities: NSFetchedResultsController<Cities>! func prepareFetchedController() {
} let request: NSFetchRequest<Countries> = Countries.fetchRequest()
var AppData = ApplicationData() let sort = NSSortDescriptor(key: "name", ascending: true, selector:
 #selector(NSString.caseInsensitiveCompare(_:)))
request.sortDescriptors = [sort]
AppData.fetchedControllerCountries = NSFetchedResultsController(fetchRequest: request,
The Table View Controllers are the same as before. All we need to do is to managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
define the Fetched Results Controller and add or remove objects from the AppData.fetchedControllerCountries.delegate = self
try? AppData.fetchedControllerCountries.performFetch()
Core Data Persistent Store. The system takes care of uploading the changes }
to CloudKit servers and updating the Persistent Store with the changes func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
introduced from other devices. The following is the view controller to list let newsnapshot = snapshot as NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>
and add countries. AppData.dataSourceCountries.apply(newsnapshot)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
Listing 17-29: Listing the countries stored in the Persistent Store performSegue(withIdentifier: "showCities", sender: self)
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
import UIKit if segue.identifier == "showCities" {
import CoreData if let path = tableView.indexPathForSelectedRow {
if let countryID = AppData.dataSourceCountries.itemIdentifier(for: path) {
class CountriesViewController: UITableViewController, NSFetchedResultsControllerDelegate { if let country = try? context.existingObject(with: countryID) as? Countries {
var context: NSManagedObjectContext! AppData.selectedCountry = country
}
override func viewDidLoad() { }
super.viewDidLoad() }
let app = UIApplication.shared }
let appDelegate = app.delegate as! AppDelegate }
context = appDelegate.persistentContainer.viewContext @IBAction func addCountry(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "Insert Country", message: nil, preferredStyle: .alert)
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "countriesCell") let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
prepareDataSource() alert.addAction(cancel)
prepareFetchedController() let action = UIAlertAction(title: "Save", style: .default, handler: { (action) in
} if let fields = alert.textFields, let text = fields.first?.text{
func prepareDataSource() { let name = text.trimmingCharacters(in: .whitespaces)
if !name.isEmpty {
let newCountry = Countries(context: self.context) var context: NSManagedObjectContext!
newCountry.name = name
try? self.context.save() override func viewDidLoad() {
super.viewDidLoad()
do { let app = UIApplication.shared
try AppData.fetchedControllerCountries.performFetch() let appDelegate = app.delegate as! AppDelegate
} catch { context = appDelegate.persistentContainer.viewContext
print("Error") tableView.register(UITableViewCell.self, forCellReuseIdentifier: "citiesCell")
} prepareDataSource()
} prepareFetchedController()
} }
}) func prepareDataSource() {
alert.addAction(action) AppData.dataSourceCities = UITableViewDiffableDataSource<Sections, NSManagedObjectID>
alert.addTextField(configurationHandler: nil) (tableView: tableView) {tableView, indexPath, cityID in
present(alert, animated: true, completion: nil) let cell = tableView.dequeueReusableCell(withIdentifier: "citiesCell", for: indexPath)
} if let city = try? self.context.existingObject(with: cityID) as? Cities {
} var config = cell.defaultContentConfiguration()
 config.text = city.name
cell.contentConfiguration = config
}
The code is the same implemented to work with Core Data before. We set return cell
up the Fetched Results Controller to get the Countries objects, and the }
diffable data source to feed the table with this data. To allow the user to }
func prepareFetchedController() {
add new countries, we create an Alert View with a Text Field and a Save let request: NSFetchRequest<Cities> = Cities.fetchRequest()
button. When the user inserts a name and taps the Save button, the request.predicate = NSPredicate(format: "country = %@", AppData.selectedCountry)
let sort = NSSortDescriptor(key: "name", ascending: true, selector:
closure for the button creates a new Countries object and saves the context. #selector(NSString.caseInsensitiveCompare(_:)))
The system updates the Persistent Store with this information, but because request.sortDescriptors = [sort]
AppData.fetchedControllerCities = NSFetchedResultsController(fetchRequest: request,
we use an NSPersistentCloudKitContainer object to manage it, the Countries managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
object is automatically turned into a CKRecord and uploaded to CloudKit AppData.fetchedControllerCities.delegate = self
try? AppData.fetchedControllerCities.performFetch()
servers. When we open the application on a second device, the process is }
inverted; the CKRecord object is downloaded from CloudKit, turned into a func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
Countries object, and added to the Persistent Store. didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
let newsnapshot = snapshot as NSDiffableDataSourceSnapshot<Sections, NSManagedObjectID>
The view controller for the second scene is similar, but this time we are AppData.dataSourceCities.apply(newsnapshot)
processing Cities objects instead. }
@IBAction func addCity(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "Insert City", message: nil, preferredStyle: .alert)
Listing 17-30: Listing the cities of a country let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(cancel)
 let action = UIAlertAction(title: "Save", style: .default, handler: { (action) in
import UIKit if let fields = alert.textFields, let text = fields.first?.text{
import CoreData let name = text.trimmingCharacters(in: .whitespaces)
if !name.isEmpty {
class CitiesViewController: UITableViewController, NSFetchedResultsControllerDelegate { let newCity = Cities(context: self.context)

newCity.name = name Connect the buttons on each scene with Actions called addCountry()
newCity.country = AppData.selectedCountry
try? self.context.save() and addCity() respectively. Update the view controllers with the codes
do { in Listings 17-29 and 17-30. Run the application in two devices. Press
try AppData.fetchedControllerCities.performFetch() the Add Country button and insert a new country. After a few
} catch {
print("Error") seconds, you should see the same value appear on the second
}
} device. Repeat the process for the cities.
}
})
alert.addAction(action)
Because we are using a Fetched Results Controller to manage the data for
alert.addTextField(configurationHandler: nil) the Table Views, the Countries and Cities objects added to the Persistent
present(alert, animated: true, completion: nil)
Store and their relationships are automatically updated and shown to the
}
} user. No matter where the new values are added from, locally or remotely,
 they always appear on the screen in every device that is running our
application. But when we are not presenting the values with a Fetched
The view controller gets the Countries object representing the country Results Controller, we must take care of updating the interface ourselves.
selected by the user from the selectedCountry property and creates a For this purpose, we must configure the Core Data stack to publish a
predicate to only retrieve the cities that belong to that country. To create notification when something changes in the Persistent Store, and perform
the relationship, we also assign this value to the country property of the new the necessary updates every time that notification is received.
Cities object when the user inserts a new city. The system uploads this
The first thing we need to control this process is a reference to the object
object to CloudKit servers and creates the connection between the records in charge of creating and loading the Persistent Store. This is an object
for us. defined by the NSPersistentStoreDescription class. The NSPersistentContainer class
includes the following property to get it.
Do It Yourself: Update the AppDelegate class with the code in Listing
17-27 (remember to replace the name in the persistentStoreDescriptions—This property returns an array
NSPersistentCloudKitContainer initializer with the name of your Core Data containing the NSPersistentStoreDescription objects in charge of every
model). Replace the initial scene with two Table View Controllers Persistent Store managed by the Core Data stack.
embedded in a Navigation Controller (Figure 17-25). Connect the
first scene to the second scene with a Show segue and identify the Once we get the NSPersistentStoreDescription object that represents the
segue with the name "showCities". Add bar buttons of type Add to Persistent Store, we must set an option in the object to get it to post
both scenes (Figure 17-25). Create a Swift file called notifications every time the content of the Persistent Store changes. The
ApplicationData.swift for the model in Listing 17-28. Create a class includes the following method for this purpose.
UITableViewController subclass call CountriesViewController for the first
scene and another called CitiesViewController for the second scene.
setOption(Value, forKey: String)—This method sets an option in if let description = container.persistentStoreDescriptions.first {
description.setOption(true as NSNumber, forKey:
the NSPersistentStoreDescription object for the key specified by the forKey NSPersistentStoreRemoteChangeNotificationPostOptionKey)
argument and with a value determined by the first argument. }
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
The Core Data framework defines a global variable called fatalError("Unresolved error \(error), \(error.userInfo)")
}
NSPersistentStoreRemoteChangeNotificationPostOptionKey with the key required to })
get the NSPersistentStoreDescription object to post notifications every time it container.viewContext.automaticallyMergesChangesFromParent = true
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
detects changes in the Persistent Store. return container
The notification posted by the NSPersistentStoreDescription object is called }()
NSPersistentStoreRemoteChange, but this notification is sent by an object that func application(_ application: UIApplication, configurationForConnecting
connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
coordinates the information between the Persistent Store and the context. UISceneConfiguration {
This object is created from the NSPersistentStoreCoordinator class. The return UISceneConfiguration(name: "Default Configuration", sessionRole:
connectingSceneSession.role)
NSManagedObjectContext class includes the following property to get a
}
reference to this object. }

persistentStoreCoordinator—This property returns a reference to


The configuration must be set before the Persistent Store is loaded, so
the NSPersistentStoreCoordinator object assigned to the context.
after the NSPersistentCloudKitContainer object is created, we get the
NSPersistentStoreDescription object representing the only Persistent Store in our
With all these tools, we can now configure the Core Data stack to send and stack (the first element of the array), and then set the
process notifications whenever something changes in the Persistent Store. NSPersistentStoreRemoteChangeNotificationPostOptionKey option with the value true
The following is a possible implementation. to get it to send notifications. Notice that the setOption() method only takes
objects that inherit from the NSObject class, so we had to convert the
Listing 17-31: Posting notifications when the Persistent Store changes Boolean value into an NSNumber object for the option to be set.

Now, we can listen to the NSPersistentStoreRemoteChange notification from any
import UIKit
import CoreData
of our view controllers and update the interface accordingly. To test this
example, we are going to generate a task from the CountriesViewController
@main class to print a message on the console when a notification is received.
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool { Listing 17-32: Updating the interface when the Persistent Store changes
return true
} 
lazy var persistentContainer: NSPersistentCloudKitContainer = { override func viewDidLoad() {
let container = NSPersistentCloudKitContainer(name: "AnotherTest") super.viewDidLoad()
let app = UIApplication.shared

let appDelegate = app.delegate as! AppDelegate Deploy to Production


context = appDelegate.persistentContainer.viewContext

tableView.register(UITableViewCell.self, forCellReuseIdentifier: "countriesCell")
prepareDataSource()
prepareFetchedController() In CloudKit's dashboard, at the bottom of the left panel, there is a list of
Task(priority: .high) { options to work with the database schema (the record types, attributes,
let center = NotificationCenter.default subscriptions, zones, etc.). We can export the schema, import a schema
for await _ in center.notifications(named: .NSPersistentStoreRemoteChange, object: nil) {
print("--- Data Updated")
from our computer, reset the schema to start from scratch, and deploy the
} schema to production. This last option is the one we need to select when
} we want to prepare our app for distribution (to be sold in the App Store).
}
 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 is like what we have done in previous examples. The viewDidLoad() This includes record types and indexes, but it does not include records
method creates an asynchronous task that runs a for in loop to read the (values added for testing). If we agree, we must press the Deploy button to
asynchronous sequence returned by the notifications() method. Every time a finish the process, and our database in CloudKit will be ready for
notification is posted by the Persistent Store, a cycle of the loop is distribution.
executed. In this example, we just print a message, but receiving these
notifications means that the Persistent Store was modified from the device IMPORTANT: The Production environment is used by apps that are
or by data received from CloudKit servers, so we should update the submitted to Apple for distribution. This step is required for your
interface accordingly. 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
Do It Yourself: Update the AppDelegate class with the code in Listing your users. To learn how to submit your app to the App Store, read
17-31. Update the viewDidLoad() method in the CountriesViewController Chapter 23.
class with the method in Listing 17-32. Run the application in two
devices. Press the Add Country button and insert a new country.
After a few seconds, you should see the same value appear on the
second device and a message on the console.
CHAPTER 18 - MEDIA 18.1 Media

These days, personal devices are primarily used for processing pictures,
videos, and sound, and Apple devices are no exception. UIKit can display an
image with an UIImageView view, but other frameworks are required to
process the image, present a video on the screen, or play sounds. In this
chapter, we introduce the tools provided by Apple for this purpose.

Camera allowsEditing—This property sets or returns a Boolean value that


determines if the user is allowed to edit the image.

videoQuality—This property sets or returns a value that determines
One of the most common uses of mobile devices is to take and store the quality of the recorded video. It is an enumeration called
photos, and that is why no device is sold without a camera anymore. QualityType included in the UIImagePickerController class. The values
Because of how normal it is for an application to access the camera and available are typeHigh, typeMedium, typeLow, type640x480, typeIFrame960x540,
manage pictures, UIKit offers a controller with built-in functionality that and typeIFrame1280x720.
provides all the tools necessary for the user to take pictures and record
videos. The class to create this controller is called UIImagePickerController. The The UIImagePickerController class also offers the following type methods to
following are some of the properties included in this class for detect the source available and the type of media it can manage.
configuration.
isSourceTypeAvailable(SourceType)—This type method returns a
sourceType—This property sets or returns a value that determines Boolean value that indicates if the source specified by the argument is
the type of source we want to use to get the pictures. It is an supported by the device. The argument is an enumeration called
enumeration called SourceType included in the UIImagePickerController SourceType included in the UIImagePickerController class. At this moment,
class. At this moment, only the value camera is available. only the value camera is available.
mediaTypes—This property sets or returns a value that determines availableMediaTypes(for: SourceType)—This type method
the type of media we want to work with. It takes an array of strings returns an array with strings that represent the media types available
with the values that represent every media we want to use. The most for the source specified by the argument. The argument is an
common values are public.image for pictures and public.movie for enumeration called SourceType included in the UIImagePickerController
videos. (These values can be represented by the constants class. At this moment, only the value camera is available.
kUTTypeImage and kUTTypeMovie.)
isCameraDeviceAvailable(CameraDevice)—This type method
cameraCaptureMode—This property sets or returns a value that returns a Boolean value that indicates if the camera specified by the
determines the capture mode used by the camera. It is an argument is available on the device. The argument is an enumeration
enumeration called CameraCaptureMode included in the called CameraDevice included in the UIImagePickerController class. The
UIImagePickerController class. The values available are photo and video.
values available are rear and front.
cameraFlashMode—This property sets or returns a value that
determines the flash mode used by the camera. It is an enumeration The UIImagePickerController class creates a new scene where the user can take
called CameraFlashMode included in the UIImagePickerController class. The pictures or record videos. After the image or the video are created, the
values available are on, off, and auto. scene must be dismissed, and the media processed. The way our code gets
access to the media and knows when to dismiss the scene is through a
delegate that conforms to the UIImagePickerControllerDelegate protocol. The
protocol includes the following methods. 

imagePickerController(UIImagePickerController, If we move the mouse over an item, the editor shows a + button to add
didFinishPickingMediaWithInfo: Dictionary)—This method is another item to the item’s hierarchy. For example, to add a new option to
called on the delegate when the user finishes taking the image or the main list, we click on the + button of the root item called "Information
recording the video. The second argument contains a dictionary with Property List" (circled in Figure 18-1). The editor now shows a list of
the information about the media. The values in the dictionary are possible options to choose from. The option to request authorization to
identified with properties of the InfoKey structure included in the use the camera is called "Privacy - Camera Usage Description". After the
UIImagePickerController class. The properties available are cropRect, option is selected, we must insert the text we want to show to the user as
editedImage, imageURL, livePhoto, mediaMetadata, mediaType, mediaURL, and
the option's value, as illustrated below.
originalImage.
Figure 18-2: Camera Usage Description option
imagePickerControllerDidCancel(UIImagePickerController)—
This method is called on the delegate when the user cancels the

process.

Once the configuration is ready, we can begin working on our app. Because
These are all the tools we need to take a picture or record a video and
the Image Picker Controller opens a new scene, the interface usually
process it. But before accessing the camera, we must ask the user for
includes an action to open it. For this example, we have decided to keep it
authorization. The process is automatic, but it requires adding an option in
simple and include just an Image View to show the image taken by the
the info.plist file that tells the user what the application is going to do with
user, and a button called Take Picture to open the scene generated by the
the camera.
controller.
We have introduced the info.plist file before. This is a file included in every
Xcode template that contains configuration values for the application. The
Figure 18-3: Interface to work with the camera
values are identified with keys and each key may contain other keys, in a
hierarchical structure. When we select this file from the Navigator Area,
Xcode shows an editor with the current values and a button at the top to
add more.

Figure 18-1: Content of the info.plist file

@IBOutlet weak var pictureView: UIImageView!

@IBAction func takePicture(_ sender: UIButton) {


let mediaPicker = UIImagePickerController()
mediaPicker.delegate = self
let sourceAvailable = UIImagePickerController.isSourceTypeAvailable(.camera)

if sourceAvailable {
mediaPicker.sourceType = .camera
mediaPicker.mediaTypes = ["public.image"]
mediaPicker.allowsEditing = false
mediaPicker.cameraCaptureMode = .photo
present(mediaPicker, animated: true, completion: nil)
} else {
print("The media is not available")
}
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo
info: [UIImagePickerController.InfoKey : Any]) {
let picture = info[.originalImage] as! UIImage
pictureView.image = picture
dismiss(animated: true, completion: nil)
}
}


When the button is pressed, the takePicture() method creates an instance of
The view controller that acts as the delegate of the UIImagePickerController the UIImagePickerController class and assigns the ViewController object as its
controller is required to conform to two protocols: delegate. Next, it checks if the camera is available and presents the
UINavigationControllerDelegate and UIImagePickerControllerDelegate. The following controller in case of success, or shows a message on the console
example includes an Action for the Take Picture button that creates, otherwise.
configures, and presents a UIImagePickerController controller to take pictures Before calling the present() method to show the scene on the screen, the
from the camera. The class also implements the method defined in the code configures the controller. The sourceType property is assigned the value
UIImagePickerControllerDelegate protocol to process the media. camera to tell the controller that we want to get a picture from the camera,
the mediaTypes property is assigned the value public.image to set images as
Listing 18-1: Taking a picture the only media we want to retrieve, the allowEditing property is set as false to
 not let the user edit the image, and the cameraCaptureMode property is
import UIKit assigned the value photo to only allow the user to take pictures. The scene
class ViewController: UIViewController, UINavigationControllerDelegate,
created by the controller is shown below (center).
UIImagePickerControllerDelegate {
Figure 18-4: Camera’s interface

The camera’s interface includes buttons to control the camera and take the
picture. After a picture is taken, a new set of buttons is shown to allow the
user to select the picture or take another one. If the user decides to use
the current picture, the controller calls the imagePickerController() method on
its delegate to report the action. This method receives a value called info
that we can read to get the media returned by the controller and process it
(store it in a 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 picture taken by the user and assign this object to the Image View to
show it on the screen (Figure 18-4, right).

Do It Yourself: Create a new project. Add an Image View and a


button to the scene (Figure 18-3). Connect the Image View to the
ViewController class with an Outlet called pictureView and the button
with an Action called takePicture(). Complete the ViewController class
with the code in Listing 18-1. Add the "Privacy - Camera Usage
Description" option to the info.plist file with the text you want to
show to the user. Run the application and press the button. Take a
picture and press the button to use it.

Storing Pictures and for videos the parameters are (video: String,
contextInfo: UnsafeRawPointer)
didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer).
In the

following example, we show the picture on the screen, store it in the Photo
Library, and open an Alert View to inform the user that the picture was
In the previous example, we show the picture on the screen, but we can
stored on the device.
store it in a file or in a Core Data Persistent Store. An alternative,
sometimes useful when working with the camera, is to store the picture in
Listing 18-2: Storing pictures in the Photo Library
the device’s Photo Library so that it is accessible to other applications. The

UIKit framework offers two functions to store images and videos.
import UIKit

UIImageWriteToSavedPhotosAlbum(UIImage, Any?, class ViewController: UIViewController, UINavigationControllerDelegate,


UIImagePickerControllerDelegate {
Selector?, UnsafeMutableRawPointer?)—This function adds the @IBOutlet weak var pictureView: UIImageView!
image specified by the first argument to the camera roll. The second
@IBAction func takePicture(_ sender: UIButton) {
argument is a reference to the object that contains the method we let mediaPicker = UIImagePickerController()
want to execute when the process is over, the third argument is a mediaPicker.delegate = self
let sourceAvailable = UIImagePickerController.isSourceTypeAvailable(.camera)
selector that represents that method, and the last argument is an
if sourceAvailable {
object with data to pass to the method.
mediaPicker.sourceType = .camera
UISaveVideoAtPathToSavedPhotosAlbum(String, Any?, present(mediaPicker, animated: true, completion: nil)
} else {
Selector?, UnsafeMutableRawPointer?)—This function adds the print("The media is not available")
video to the camera roll at the path indicated by the first argument. }
}
The second argument is a reference to the object that contains the func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo
method we want to execute when the process is over, the third info: [UIImagePickerController.InfoKey : Any]) {
let picture = info[.originalImage] as! UIImage
argument is a selector that represents that method, and the last pictureView.image = picture
UIImageWriteToSavedPhotosAlbum(picture, self,
argument is an object with additional data for the method. #selector(confirmImage(image:didFinishSavingWithError:contextInfo:)), nil)
dismiss(animated: true, completion: nil)
These functions store the picture or video taken by the camera in the }
@objc func confirmImage(image: UIImage, didFinishSavingWithError error: NSError?,
Photo Library and then call a method to report the result of the operation. contextInfo: UnsafeRawPointer) {
We can declare the target object and the selector as nil if we don't want to if error == nil {
let alert = UIAlertController(title: "Picture Saved", message: "The picture was added to
know what happened, or define a method in our view controller to handle your photos", preferredStyle: .alert)
the response. This method can have any name we want, but it is required let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
to have a specific definition that includes three parameters. For images, present(alert, animated: true, completion: nil)
the parameters are (image: UIImage, didFinishSavingWithError error: NSError?, } else {
print("Error") Photo Library
}
} 
}

The same way there is a predefined view controller for the camera, there is
When we get the media from the Photo Library, the system takes care of also another for the Photo Library. This controller is created from the
the security aspects for us, but when we try to store new pictures or videos PHPickerViewController class, defined in the PhotosUI framework. The class
in the device, we must configure the app to ask the user for authorization. includes the following initializer.
As always, this is done from the info.plist file. In this case, we must add the
option "Privacy - Photo Library Additions Usage Description" with the PHPickerViewController(configuration:
message we want to show to the user when authorization is requested. PHPickerConfiguration)—This initializer creates a
object with the configuration specified by the
PHPickerViewController
Do It Yourself: Update the ViewController class with the code in configuration argument.
Listing 18-2. Add the option "Privacy - Photo Library Additions Usage
Description" to the info.plist file to get access to the Photo Library. The initializer requires a PHPickerConfiguration structure for configuration. The
Run the application and take a picture. You should see an Alert View structure includes the following initializer.
with the message "Picture Saved" and the picture should be
available in your device’s Photo Library. PHPickerConfiguration(photoLibrary: PHPhotoLibrary)—This
initializer creates a PHPickerConfiguration structure to access the Photo
Library. The PHPhotoLibrary class includes a type method called shared()
to return a reference to the device's Photo Library.

The PHPickerConfiguration structure also includes the following properties to


specify the configuration.

filter—This property sets or returns a value that determines the type


of media displayed by the controller. It is a structure of type
PHPickerFilter with the properties images, livePhotos, and videos.

selectionLimit—This property sets or returns an integer value that


determines the number of resources the user can select from the
picker. A value of 0 allows the user to select unlimited resources.

selection—This property sets or returns a value that determines the operations, but it is also implemented by the PHPickerViewController class to
type of selection to perform when multiple selection is allowed. It is return the images or videos selected by the user. The NSItemProvider class
an enumeration called Selection with the values ordered and default. includes the following methods for this purpose.
preselectedAssetIdentifiers—This property sets or returns an
canLoadObject(ofClass: Class)—This method returns a Boolean
array with strings that identify the selected items. It is used to show
value that indicates whether the resource contained by the
the picker with preselected items.
NSItemProvider object can be converted to an object of the class
specified by the ofClass argument.
Like the UIImagePickerController class, the PHPickerViewController class calls a
method on its delegate to provide information about the media selected loadObject(ofClass: Class, completionHandler: Closure)—This
by the user. The class includes the delegate property to set this delegate and method asynchronously loads the resource contained by the
the framework includes the PHPickerViewControllerDelegate protocol, which NSItemProvider object, cast the object to the class specified by the
defines the following method. ofClass argument, and calls a closure with the result. The closure
receives two values: an object that conforms to the
picker(PHPickerViewController, didFinishPicking: NSItemProviderReading protocol with the actual data, and a second value
[PHPickerResult])—This method is called on the delegate after the to report errors.
user selects an image or a video from the picker. The didFinishPicking
argument is an array of PHPickerResult structures representing each The PHPickerViewController class generates a modal scene to allow the user to
asset selected by the user. select an image or a video from the Photo Library, so we also have to
present the scene with the present() method, as we did for the
The resources selected by the user are returned as PHPickerResult structures. UIImagePickerController controller. But first, we must define a
This is a wrapper with information about the asset and an NSItemProvider PHPickerConfiguration object to configure the picker, as shown in the following
object with the actual resource. The PHPickerResult structure includes two example.
properties to return these values.
Listing 18-3: Selecting a picture from the Photo Library
itemProvider—This property returns the NSItemProvider object with 
the images or videos selected by the user. import UIKit
import PhotosUI
assetIdentifier—This property returns a string with the name
assigned to the resource. class ViewController: UIViewController, PHPickerViewControllerDelegate {
@IBOutlet weak var pictureView: UIImageView!

The NSItemProvider class was design to transmit resources, such as data or @IBAction func takePicture(_ sender: UIButton) {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
files, between services. It is used during drag and drop or copy and paste configuration.filter = .images
configuration.selectionLimit = 1 an image), and call the loadObject(ofClass:) method to load the image. The
let picker = PHPickerViewController(configuration: configuration) result is shown below.
picker.delegate = self
present(picker, animated: true)
Figure 18-5: Photo Library’s interface (center)
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if let itemProvider = results.first?.itemProvider {
if itemProvider.canLoadObject(ofClass: UIImage.self) {
itemProvider.loadObject(ofClass: UIImage.self) { item, error in
if let image = item as? UIImage {
Task(priority: .background) {
await MainActor.run {
self.pictureView.image = image
}
}
}
}
}
}
dismiss(animated: true, completion: nil) 
}
}
 By default, the PHPickerViewController is presented in a full screen sheet, but
we can take advantage of the bigger screens offered by iPads to show the
In this example, we import the PhotosUI framework to be able to present a scene with a different format, such as a popover. All we need to do is to
PHPickerViewController controller and get our view controller to conform to configure the presentation style of the view controller and set the required
the PHPickerViewControllerDelegate protocol to process the response. The code parameters. The following example shows the scene in a popover.
assumes that we are using the same interface introduced in Figure 18-3.
When the user presses the Take Picture button, we define the Listing 18-4: Showing the picker in a popover
PHPickerConfiguration class to configure the picker to show images and allow 
the user to select only one. Next, the PHPickerViewController object is created, import UIKit
import PhotosUI
and the picker is presented on the screen.
When the user selects a resource, the PHPickerViewController object calls the class ViewController: UIViewController, PHPickerViewControllerDelegate {
protocol method on its delegate. The method receives an array of @IBOutlet weak var pictureView: UIImageView!
@IBOutlet weak var pictureButton: UIButton!
PHPickerResult structures with all the resources selected by the user, but
because our picker was configured to only allow the user to select one @IBAction func takePicture(_ sender: UIButton) {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
picture, we read the first value to get the NSItemProvider object that contains configuration.filter = .images
this asset. Next, we check that the asset can be converted into a UIImage configuration.selectionLimit = 1
object with the canLoadObject() method (making sure that the user selected
let picker = PHPickerViewController(configuration: configuration)

picker.delegate = self press the Take Picture button. The Photo Library should open in a
picker.modalPresentationStyle = .popover popover.
if let popover = picker.popoverPresentationController {
popover.sourceView = pictureButton
popover.sourceRect = pictureButton.bounds In these examples, we have set the selectionLimit property to 1, so the user
popover.permittedArrowDirections = [.up] could only select one picture at a time, but the PHPickerViewController allows
}
present(picker, animated: true)
multiple selection. All we need to do is to assign the value 0 to the
} selectionLimit property and the feature is enabled. But if we want to allow
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { the user to modify the selection later, we must store the selected pictures
if let itemProvider = results.first?.itemProvider {
if itemProvider.canLoadObject(ofClass: UIImage.self) { in a model and configure the picker with these values so that the user can
itemProvider.loadObject(ofClass: UIImage.self) { item, error in see which pictures were selected before and make changes. There are
if let image = item as? UIImage {
Task(priority: .background) { multiple ways to achieve this. For our example, we are going to use a
await MainActor.run { simple model, like those implemented before, with a custom class to store
self.pictureView.image = image
}
all the information.
}
} Listing 18-5: Defining the model to keep track of the selection
}
} 
} import UIKit
dismiss(animated: true, completion: nil)
} enum Sections {
} case main
 }
class ItemsData: Identifiable {
let id: String!
In this example, we anchor the popover to the Take Picture button, so the let image: UIImage!
scene is shown below the button, close to where the user’s finger is. If we let title: String!
open the scene on a device with a small screen, the system shows it full
init(_ id: String, _ image: UIImage, _ title: String) {
screen, but if we do it on an iPad, the system creates a popover self.id = id
presentation controller and assigns it to the popoverPresentationController self.image = image
self.title = title
property. If this property is not nil, we set the values for the presentation }
and the scene is presented as a popover (see Action Sheets in Chapter 13, }
Listing 13-6). class ApplicationData {
var listPictures: [ItemsData] = []
var dataSource: UITableViewDiffableDataSource<Sections, ItemsData.ID>!
Do It Yourself: Connect the Take Picture button to the ViewController }
var AppData = ApplicationData()
class with an Outlet called pictureButton. Update the ViewController class 
with the code in Listing 18-4. Run the application on an iPad and
The ItemsData class includes three properties: the id property to store the import PhotosUI
picture's identifier, a UIImage object with the image, and a string with the class PicturesViewController: UITableViewController, PHPickerViewControllerDelegate {
name suggested by the picker for the picture. As always, we store these override func viewDidLoad() {
super.viewDidLoad()
values in an array and define the diffable data source with the id property.
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell")
To display the list of pictures selected by the user, we need a small prepareDataSource()
interface with a Table View Controller embedded in a Navigation Controller prepareSnapshot()
}
and a button in the Navigation Bar to open the picker. func prepareDataSource() {
AppData.dataSource = UITableViewDiffableDataSource<Sections, ItemsData.ID>(tableView:
tableView) { tableView, indexPath, itemID in
Figure 18-6: Interface to test multiple selection let cell = self.tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.listPictures.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
config.text = item.title
config.image = item.image
config.imageProperties.maximumSize = CGSize(width: 100, height: 80)
config.imageProperties.cornerRadius = 10
config.imageProperties.reservedLayoutSize = CGSize(width: 100, height: 80)
cell.contentConfiguration = config
}
return cell
}
}
func prepareSnapshot() {
var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(AppData.listPictures.map({ $0.id }))
AppData.dataSource.apply(snapshot)
}
@IBAction func takePicture(_ sender: UIBarButtonItem) {
 var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.filter = .images
configuration.selectionLimit = 0
The purpose of the application is to show the list of pictures selected by configuration.preselectedAssetIdentifiers = AppData.listPictures.map { $0.id }
the user on the table and allow the user to open the picker and change the
selection at any time. The pictures are stored in an array in the model, and let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
each picture is identified by a unique identifier assigned by the picker. The present(picker, animated: true)
following is a possible implementation for the view controller. }
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
AppData.listPictures = AppData.listPictures.filter { value in
Listing 18-6: Displaying the pictures selected by the user if results.contains(where: { $0.assetIdentifier == value.id }) {
return true

} else {
import UIKit return false

} The code begins by defining the diffable data source and the snapshot,
}
prepareSnapshot() with only a minor change from previous examples. This is because the
images retrieved from the Photo Library may be of different sizes, so we
for result in results {
if !AppData.listPictures.contains(where: { $0.id == result.assetIdentifier }) {
set the reservedLayoutSize property to configure the size of the column and
loadItem(item: result) make sure the pictures and the text are aligned.
} The picker is configured as before, but now we assign the value 0 to the
}
dismiss(animated: true, completion: nil) selectionLimit property to allow multiple selection and an array of identifiers
} to the preselectedAssetIdentifiers property so that the pictures previously
func loadItem(item: PHPickerResult) {
let provider = item.itemProvider
selected by the user are already selected in the picker.
let fileName = provider.suggestedName ?? "Undefined" In the delegate method, we perform two operations. First, we filter the
let assetID = item.assetIdentifier ?? "" values in the model. This is to remove from the model the pictures that
if provider.canLoadObject(ofClass: UIImage.self) { were deselected by the user, so if the user selects a picture and then opens
provider.loadObject(ofClass: UIImage.self) { item, error in the picker again and deselects it, the picture is removed from the model
if let image = item as? UIImage {
if let thumbnail = self.getThumbnail(image: image) { and not shown on the table anymore. After the listPictures array is filtered,
let newItem = ItemsData(assetID, thumbnail, fileName) we update the snapshot and then proceed to load the new items selected
AppData.listPictures.append(newItem)
Task(priority: .high) {
by the user. To simplify the code, we moved the loading process to another
await MainActor.run { method called loadItem(). In this method, we get the name and the identifier
self.prepareSnapshot() assigned to the picture from the suggestedName and assetIdentifier properties,
}
} then check if the image can be extracted from the asset as we did in
} previous examples, and finally create the new ItemsData object and add it to
}
}
the model. Notice that so save resources, we do not store the original
} image in the model, we create a thumbnail with the preparingThumbnail()
}
method and store this image instead (see Images in Chapter 5).
func getThumbnail(image: UIImage) -> UIImage? {
let imageWidth = image.size.width
let imageHeight = image.size.height Figure 18-7: Multiple selection
let scaleFactor = (imageWidth > imageHeight) ? 100 / imageWidth : 80 / imageHeight
if let thumbnail = image.preparingThumbnail(of: CGSize(width: imageWidth * scaleFactor,
height: imageHeight * scaleFactor)) {
return thumbnail
} else {
return nil
}
}
}

Custom Camera

The UIImagePickerController controller is built from classes defined in the AV


Foundation framework. This framework provides the codes necessary to
process media and control input devices, like the camera and the
microphone. Thus, we can use the classes in this framework directly to
build our own controller and customize the process and the interface.
Creating our own controller to access the camera and retrieve information
 demands the manipulation and coordination of several systems. We need
to configure the input from the camera and the microphone, process the
data received from these inputs, show a preview to the user, and generate
Do It Yourself: Create a new project. Replace the scene with a
the output in the form of an image, live photo, video, or audio. Figure 18-8
Table View Controller embedded in a Navigation Controller. Add a
illustrates all the elements involved.
Bar Button Item to the Navigation Bar (see Figure 18-6). Create a
Swift file called ApplicationData.swift for the model in Listing 18-5. Figure 18-8: System to capture media
Create a subclass of UITableViewController called PicturesViewController and
assign it to the Table View Controller. Connect the Bar Button Item to
this class with an Action called takePicture(). Complete the class with
the code in Listing 18-6. Run the application, press the button, and
select a few pictures. Press the Add button. You should see the
selected pictures on the table (Figure 18-7). Open the picker again
and deselect some pictures. The deselected pictures should be 
removed from the table.
The first thing we need to do to build this structure is to determine the
input devices. The AV Foundation framework defines the AVCaptureDevice
class for this purpose. An instance of this class can represent any type of
input device, including cameras and microphones. The following are some
of the methods included in the class to access and manage a device.

default(for: AVMediaType)—This type method returns an


AVCaptureDevice object that represents the default capture device for

the media specified by the argument. The for argument is a structure and AVCaptureAudioDataOutput to get the audio, but the most frequently used
of type AVMediaType with properties to define the type of media. The is the AVCapturePhotoOutput to capture a single video frame (take a picture).
properties available to work with the cameras and microphones are This class works with a delegate that conforms to the
video and audio. AVCapturePhotoCaptureDelegate protocol, which among other methods defines
the following to return a still image.
requestAccess(for: AVMediaType)—This asynchronous type
method asks the user for permission to access the device. The for
photoOutput(AVCapturePhotoOutput,
argument is a structure of type AVMediaType with properties to define
didFinishProcessingPhoto: AVCapturePhoto, error: Error?)—
the type of media. The properties available to work with the cameras This method is called on the delegate after the image is captured. The
and microphones are video and audio. didFinishProcessingPhoto argument is a container with information
authorizationStatus(for: AVMediaType)—This type method about the image, and the error argument is used to report errors.
returns a value that determines the status of the authorization to use
the device. The for argument is a structure of type AVMediaType with To control the flow of data from input to output, the framework defines
properties to define the type of media. The properties available to the AVCaptureSession class. From an instance of this class, we can control the
work with the cameras and microphones are video and audio. The inputs and outputs and determine when the process begins and ends by
method returns an enumeration of type AVAuthorizationStatus with the calling the following methods.
values notDetermined, restricted, denied, and authorized.
addInput(AVCaptureInput)—This method adds an input to the
An instance of the AVCaptureDevice class represents a capture device. To capture session. The argument represents the input device we want
define this device as an input device, we must create an object that to add.
controls the ports and connections. The framework defines the addOutput(AVCaptureOutput)—This method adds an output to
AVCaptureDeviceInput class for this purpose. The class includes the following the capture session. The argument represents the output we want to
initializer to create the input object for the device. generate from the capture session.

AVCaptureDeviceInput(device: AVCaptureDevice)—This startRunning()—This method starts the capture session.


initializer creates an input for the device specified by the device stopRunning()—This method stops the capture session.
argument.
The framework also defines the AVCaptureVideoPreviewLayer class to show a
In addition to inputs, we also need outputs to process the data captured by preview to the user. This class creates a sublayer to display the video
the device. The framework defines subclasses of a base class called captured by the input device. The class includes the following initializer
AVCaptureOutput to describe the outputs. There are several subclasses and properties to create and manage the preview layer.
available, such as AVCaptureVideoDataOutput to process the frames of a video,
AVCaptureVideoPreviewLayer(session: AVCaptureSession)— the bottom added over the preview view with a button to let the user take
This initializer creates an AVCaptureVideoPreviewLayer object with a a picture.
preview layer connected to the capture session defined by the session
argument. Figure 18-9: Custom controller for the camera

videoGravity—This property defines how the video adjust its size to


the size of the preview layer. It is an enumeration of type
AVLayerVideoGravity with the values resizeAspect, resizeAspectFill, and resize.

connection—This property returns an object of type


that defines the connection between the capture
AVCaptureConnection
session and the preview layer.


The input, output, and preview layers are connected to the capture session
by objects of the AVCaptureConnection class. The class manages the
information of the connection, including ports and data. The following are
Do It Yourself: Create a new project. Embed the initial scene in a
some of the properties provided by this class. Navigation Controller. Add an Image View and a bar button to the
initial scene (Figure 18-9, center). Add a new scene to the
videoOrientation—This property sets or returns the orientation of Storyboard. Connect the bar button to the second scene with a Show
the video. It is an enumeration of type AVCaptureVideoOrientation with segue. Add an empty view to the second scene with a gray
the values portrait, portraitUpsideDown, landscapeRight, and landscapeLeft. background and a Height constraint of 60 points (the option in the
Library to add empty views is called View). Click on the background
isVideoOrientationSupported—This property returns a Boolean
color of this view and select the option Custom to open the color
value that determines whether it is possible to set the video
picker. Set an Opacity value of 50% to make it translucent. Pin this
orientation or not.
view to the bottom of the main view and add a button at the center
Because the scene is not generated by a controller anymore, we must (Figure 18-9, right). Select an SF Symbol for the button from the
create our own. Figure 18-9, below, introduces the interface we have Attributes Inspector panel. Add another empty view for the preview
created for this example. The initial scene was embedded in a Navigation layer and pin it to the edges of the main view to adopt the size of the
Controller and an additional scene was included to manage the camera. screen (highlighted in Figure 18-9, right). Move this view to the back
The initial scene includes an Image View to show the picture taken by the from the Document Outline panel or from the Editor menu to
user, and a bar button to open the second scene. The second scene has an position it behind the toolbar.
empty view to which we will add the preview layer, and a narrow view at

The view controller for the initial scene only needs to get the picture from var previewLayer: AVCaptureVideoPreviewLayer!
var picture: UIImage!
the second scene and show it on the screen. For this example, we have var imageOrientation: UIImage.Orientation!
decided to create an Action that we are going to connect to an Unwind
override func viewDidLoad() {
Segue to get the image from the second scene and assign it to the Image super.viewDidLoad()
View (the image will be stored in a property called picture). let device = UIDevice.current
device.beginGeneratingDeviceOrientationNotifications()

Listing 18-7: Displaying the picture on the screen let status = AVCaptureDevice.authorizationStatus(for: .video)
 if status == .authorized {
prepareCamera()
import UIKit } else if status == .notDetermined {
Task(priority: .high) {
class ViewController: UIViewController {
await askAuthorization()
@IBOutlet weak var pictureView: UIImageView! }
} else {
@IBAction func goBack(_ segue: UIStoryboardSegue) {
notAuthorized()
let controller = segue.source as! CameraViewController }
pictureView.image = controller.picture }
}
func askAuthorization() async {
} let granted = await AVCaptureDevice.requestAccess(for: .video)
 await MainActor.run {
if granted {
For the second scene, we have created a subclass of UIViewController called self.prepareCamera()
} else {
CameraViewController. In this view controller, we must follow a series of steps self.notAuthorized()
to activate the camera, let the user take a picture, and process the image. }
}
But before even accessing the camera we must ask the user for permission. }
This is done automatically when we use a UIImagePickerController controller, }
but in a custom controller we must do it ourselves with the type methods 

provided by the AVCaptureDevice class, as shown next.


The authorizationStatus() method returns an AVAuthorizationStatus value to inform
Listing 18-8: Asking for permission to use the camera the current status of the authorization. If the value is authorized, it means

that we have been previously authorized and can use the camera, but if
the value is notDetermined, we must call the requestAccess() method to ask for
import UIKit
import AVFoundation permission. This is an asynchronous method, so we create a task and
implement it to ask for permission to use the camera. After the user
class CameraViewController: UIViewController, AVCapturePhotoCaptureDelegate {
@IBOutlet weak var cameraView: UIView! responds, the method returns a value of type Bool to report the result.
We use two methods to respond to each situation. The prepareCamera()
var captureSession: AVCaptureSession!
var stillImage: AVCapturePhotoOutput!
method is executed every time the code detects that we are authorized to
use the camera, and the notAuthorized() method is executed otherwise. In the We can create and add to the session all the inputs and outputs we need,
following implementation of the latter, we show and Alert View to inform in any possible order, but because the AVCaptureDeviceInput() initializer
the situation to the user. throws an error, we use it first. This initializer creates an object that
manages the input for the capture device. If the initializer is successful, we
Listing 18-9: Informing the user that the camera is not available add it to the capture session with the addInput() method and then create the
 output. For this example we have decided to use the session to capture a
func notAuthorized() { still image, so we use the AVCapturePhotoOutput class to create the output
let alert = UIAlertController(title: "No Camera", message: "This app is not authorized to use the and add it to the session with the addOutput() method.
camera. You can authorize access from the Settings app.", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil) After adding the inputs and outputs to the capture session, the
alert.addAction(action) prepareCamera() method executes an additional method called showCamera() to
present(alert, animated: true, completion: nil)
} generate the preview layer and show the video generated by the camera
 on the screen. In this method, we create the layer and set its size and
orientation.
The prepareCamera() method is where we begin to build the network of
objects introduced in Figure 18-8. This method gets a reference to the Listing 18-11: Displaying the video from the camera
default capture device for video and creates the inputs and outputs we 
need to capture a still image (to take a picture). func showCamera() {
view.setNeedsLayout()
view.layoutIfNeeded()
Listing 18-10: Initializing the camera let width = cameraView.bounds.size.width
 let height = cameraView.bounds.size.height
func prepareCamera() {
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
captureSession = AVCaptureSession()
previewLayer.videoGravity = .resizeAspectFill
if let device = AVCaptureDevice.default(for: AVMediaType.video) {
previewLayer.frame = CGRect(x: 0, y: 0, width: width, height: height)
if let input = try? AVCaptureDeviceInput(device: device) {
captureSession.addInput(input)
let videoOrientation = getCurrentOrientation()
stillImage = AVCapturePhotoOutput()
let connection = previewLayer.connection
captureSession.addOutput(stillImage)
connection?.videoOrientation = videoOrientation
showCamera()
} else {
let layer = cameraView.layer
notAuthorized()
layer.addSublayer(previewLayer)
}
captureSession.startRunning()
} else {
}
notAuthorized()

}
}
 The AVCaptureVideoPreviewLayer() initializer creates a layer that we have to
adjust to the size of its view and add as a sublayer of the current view’s

layer. But setting the position and size of the layer does not determine how 
its content is going to be shown. The video coming from the camera could func getCurrentOrientation() -> AVCaptureVideoOrientation {
have a different size and orientation. How the video is going to adjust to var currentOrientation: AVCaptureVideoOrientation!
let deviceOrientation = UIDevice.current.orientation
the size of the layer is determined by the value of the layer’s videoGravity
property, but the orientation is set from the connection between the switch deviceOrientation {
case .landscapeLeft:
capture session and the preview layer. This is the reason why, after setting currentOrientation = AVCaptureVideoOrientation.landscapeRight
the value of the videoGravity property and the layer’s frame, we get a imageOrientation = .up
case .landscapeRight:
reference to the connection from the layer’s connection property. By
currentOrientation = AVCaptureVideoOrientation.landscapeLeft
modifying the videoOrientation property of the connection, we can adjust the imageOrientation = .down
orientation according to the device’s orientation and finally add the case .portrait:
currentOrientation = AVCaptureVideoOrientation.portrait
sublayer to the view’s layer with the addSublayer() method. When the imageOrientation = .right
sublayer is ready, the capture session can be initiated with the startRunning() case .portraitUpsideDown:
currentOrientation = AVCaptureVideoOrientation.portraitUpsideDown
method. imageOrientation = .left
default:
if UIDevice.current.orientation.isLandscape {
IMPORTANT: When a view draws its content, the graphics are currentOrientation = AVCaptureVideoOrientation.landscapeRight
stored in a layer that can be processed by the hardware and imageOrientation = .up
} else {
presented on the screen. The view layer is created from a class currentOrientation = AVCaptureVideoOrientation.portrait
provided by the Core Animation framework called CALayer. In iOS, imageOrientation = .right
}
every view automatically includes a layer. To provide access to this break
layer, the UIView class includes the layer property (implemented in }
return currentOrientation
Listing 18-11). For more information, visit our website and follow the }
links for this chapter. 

To determine the orientation, we define a method called IMPORTANT: The camera always encodes the image in its native
getCurrentOrientation(). We need to know the device's orientation to be able orientation, which is landscape-right. In consequence, when the
to define the orientation of the preview layer and the orientation of the device is in portrait mode, we must set the orientation of the image
image taken by the camera. For these reasons, our method returns the to right, when it is in landscape-left mode, we must set the image's
AVCaptureVideoOrientation value we need to set the orientation of the preview orientation to up, and when it is in landscape-right mode, we must
layer and stores a UIImage.Orientation value in the imageOrientation property to set it to down. Also, the landscape orientation of the video is the
set the image's orientation later. opposite of the device, therefore when the device is in the
landscape-right orientation, the video orientation is landscape-left,
Listing 18-12: Detecting the device's orientation
and vice versa.
previewPhotoFormat—This property set or returns a dictionary
At this point, the video is playing on the screen and the system is ready to with keys and values that determine the characteristics of the preview
perform a capture. To allow the user to take a picture, we must connect image. The keys available are kCVPixelBufferPixelFormatTypeKey
the button added at the bottom of the view (Figure 18-9, right) to an (uncompressed format), kCVPixelBufferWidthKey (maximum width) and
Action in the CameraViewController class. The process to capture an image is
kCVPixelBufferHeightKey (maximum height).
initiated by the output object. The AVCapturePhotoOutput class we use to
capture a still image 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 isHighResolutionPhotoEnabled—This property is a Boolean value
capture with the settings specified by the with argument. The 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 prepare the settings with an AVCapturePhotoSettings
data generated by the output. object, call the capturePhoto() method of the AVCapturePhotoOutput object, and
define the delegate method that is going to process the image returned.
The type of photo captured by the output is determined by an The following example implements the delegate method and an Action to
AVCapturePhotoSettings object. The class includes multiple initializers. The initiate the process when the user presses the button.
following are the most frequently used.
Listing 18-13: Taking a picture
AVCapturePhotoSettings()—This initializer creates an 
@IBAction func takePicture(_ sender: UIButton) {
AVCapturePhotoSettings object with the format by default. let settings = AVCapturePhotoSettings()
AVCapturePhotoSettings(format: Dictionary)—This initializer stillImage.capturePhoto(with: settings, delegate: self)
}
creates a AVCapturePhotoSettings object with the format specified by the func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo:
format argument. The argument is a dictionary with keys and values AVCapturePhoto, error: Error?) {
let scale = UIScreen.main.scale
to set the characteristics of the image. Some of the keys available are
if let imageData = photo.cgImageRepresentation() {
kCVPixelBufferPixelFormatTypeKey (uncompressed format), AVVideoCodecKey
picture = UIImage(cgImage: imageData, scale: scale, orientation: imageOrientation)
(compressed format), AVVideoQualityKey (compression quality). performSegue(withIdentifier: "goBackSegue", sender: self)
}
}
The following are some of the properties available in this class to configure 
the image and the preview.

When the user presses the button to take the picture, the takePicture() the orientation of the device. As we have done before for other projects in
method calls the capturePhoto() method to ask the output object to capture this book, we can implement the viewWillTransition() method to get the new
an image. After the image is captured, this object sends the result to the size of the main view and adjust the preview layer accordingly.
delegate method. The value received by the method is an object of type
AVCapturePhoto, which is a container with information about the image. The Listing 18-14: Rotating the preview layer
class includes two convenient methods to get the data representing the 
image. override func viewWillTransition(to size: CGSize, with coordinator:
UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
fileDataRepresentation()—This method returns a data if previewLayer != nil {
representation of the image that we can use to create a UIImage previewLayer.frame.size = size

object. let videoOrientation = getCurrentOrientation()


let connection = previewLayer.connection
cgImageRepresentation()—This method returns the image as a connection?.videoOrientation = videoOrientation
CGImage object. }
}

In our example, we have implemented the cgImageRepresentation() method.
This method returns a CGImage object that we can use to create the UIImage Do It Yourself: Update the ViewController class with the code in
object. The CGImage class is defined by the Core Graphics framework to Listing 18-7. Create a subclass of UIViewController called
provide a low-level representation of images. The reason why we decided CameraViewController and assign it to the second scene. Connect the
to create the UIImage object from a CGImage value is because the UIImage view for the preview layer with an Outlet called cameraView. Complete
class includes a convenient initializer that takes this object and the image's the class with the code in Listing 18-8. Add the methods in Listings
scale and orientation and returns a UIImage object with that configuration
18-9, 18-10, 18-11, and 18-12 to this class. Connect the button in the
(see Images in Chapter 5). In the view controller of Listing 18-13, the scale
second scene to an Action called takePicture() and complete the
is taken from the screen scale, and the orientation is provided by the
imageOrientation property. This sets the image scale the same as the screen
method with the code in Listing 18-13. Complete the
and the orientation according to the orientation of the device when the CameraViewController class with the method in Listing 18-14. Create an

picture was taken, so the image is always displayed correctly. Unwind Segue between this scene and the goBack() method of the
After the image is processed, we trigger the Unwind Segue identified with ViewController class and give it the identifier "goBackSegue".
the string "goBackSegue" to take the user back to the initial scene. This is Remember to add the option "Privacy - Camera Usage Description"
the Unwind Segue we must create for the goBack() Action included in the to the info.plist file. Run the application and take a picture.
ViewController class of Listing 18-7.
The controller for the camera is ready, but there is one more method we
need to add to the CameraViewController class to adapt the preview layer to
AVKit Framework rate—This property sets or returns a Float value that determines the
rate at which the media is being played. A value of 0.0 pauses the

video and 1.0 sets the normal rate.
In addition to the scene created by the UIImagePickerController class to take play()—This method begins playback.
pictures, we can also create a scene with standard controls to play videos. pause()—This method pauses playback.
The framework that provides this feature is called AVKit, and the class is
called AVPlayerViewController. This class creates a player with the controls
addPeriodicTimeObserver(forInterval: CMTime, queue:
necessary for the user to play a video. The following are some of its DispatchQueue?, using: Closure)—This method adds an observer
properties, used to define and configure the player. that executes a closure every certain period of time. The forInterval
argument determines the time between executions, the queue
player—This property sets the player that provides the media for the argument is the queue in which the closure should be executed (the
controller to play. It is an object of type AVPlayer. main queue is recommended), and the using argument is the closure
showsPlaybackControls—This property is a Boolean value that we want to execute. The closure receives a value of type CMTime with
the time at which the closure was called.
determines whether the player is going to show the controls or just
the video.
The following example shows how to create, configure, and open an
AVPlayerViewController controller.
The controller defines the player and presents the interface for the user to
control the video, but the video is played by an object of the AVPlayer class.
Listing 18-15: Using a standard video player
The class includes the following initializer to use with AVKit.

import UIKit
AVPlayer(url: URL)—This initializer creates an AVPlayer object to play import AVFoundation
the media stored in the URL indicated by the argument. import AVKit

class ViewController: UIViewController {


The AVPlayer class also includes properties and methods to control the @IBAction func playVideo(_ sender: UIButton) {
let bundle = Bundle.main
playback. let videoURL = bundle.url(forResource: "trailer", withExtension: "mp4")

let player = AVPlayer(url: videoURL!)


volume—This property sets or returns a value that determines the let controller = AVPlayerViewController()
player’s volume. It is a value of type Float between 0.0 and 1.0. controller.player = player
present(controller, animated: true, completion: {
isMuted—This property is a Boolean value that determines whether player.play()
the player’s audio is muted or not. })
}
}

This example assumes that we have a button in the initial scene connected
to an Action in the ViewController class called playVideo(). When the button is
pressed, the method creates the AVPlayer object from a URL, initializes the
AVPlayerViewController controller, assigns the player object to the controller’s
player property, and presents the controller on the screen. In this example
we use the completion handler of the present() method to play the video as
soon as the view is loaded, but we can let the user decide.

Figure 18-10: AVKit video player


© Copyright 2008, Blender Foundation / www.bigbuckbunny.org

Do It Yourself: Create a new project. Add a button called Play


Video to the initial scene. Connect the button to an Action called
playVideo(). Complete the ViewController class with the code in Listing
18-15. Download the trailer.mp4 file from our website and add it to
your project (remember to mark the option Add to Target). Run the
application and press the button. A new scene is shown on the
screen with a standard video player. Press the X button in the view to
remove it.
Custom Video Player AVPlayerItem(asset: AVAsset)—This initializer creates an
AVPlayerItem object to represent the asset defined by the asset

argument.
The standard video player created by the AVKit framework is designed with
classes defined by the AVFoundation framework. There is a class in charge The AVPlayerItem class also includes properties and methods to control the
of the asset (video or audio), a class in charge of providing the media to the asset's status. The following are the most frequently used.
player, a class in charge of playing the media, and a class in charge of
displaying the media on the screen. By implementing these classes, we can status—This property returns a value that indicates the status of the
create our own player. Figure 18-11, below, shows the required structure. player item. It is an enumeration called Status with the values unknown,
readyToPlay, and failed.
Figure 18-11: System to play media duration—This property returns a value that indicates the duration
of the player item. It is a structure of type CMTime.
currentTime()—This method returns a CMTime value with the item's
current time.
seek(to: CMTime)—This asynchronous method moves the playback
 cursor to the time specified by the to argument.

Let's see the elements one by one, starting from the asset. An asset is The AVPlayerItem object manages the information necessary for playback
composed of one or more tracks of media, including video, audio, subtitles, but it does not play the media; this is done by an instance of the AVPlayer
etc. The AVFoundation framework defines a class called AVAsset to load an class. This is the same class we used with the AVPlayerViewController object to
asset. The class includes the following initializer. play videos. The class includes the following initializer to create a player
from an AVPlayerItem object.
AVAsset(url: URL)—This initializer creates an AVAsset object with the
media in the location indicated by the url argument. The argument is AVPlayer(playerItem: AVPlayerItem)—This initializer creates an
a URL structure with the location of a local or remote resource. object to play the media represented by the playerItem
AVPlayer
argument.
An asset contains static information and cannot manage its status when it
is being played. To control the asset, the framework defines the AVPlayerItem The last object required by the structure is the one in charge of displaying
class. With this class, we can reference an asset and manage its timeline. the media on the screen. This is a subclass of the CALayer class called
The class includes multiple initializers, including the following. AVPlayerLayer that provides the code necessary to draw the frames. The

class includes the following initializer and property to create and configure value of 1 preserves the value in seconds assigned to the first
the layer. argument.
zero—This type property returns a CMTime structure with a value of 0.
AVPlayerLayer(player: AVPlayer)—This initializer creates an
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 in seconds. It is of type
type properties resize, resizeAspect, and resizeAspectFill. 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
timescale—This property returns the time scale of a CMTime
need a way to control time. Because the precision of floating-point values
structure.
is not suitable for media playback, the framework implements, among
other things, the CMTime structure from an old framework called Core
Media. The structure contains multiple values to represent time as a The process to create a player is straightforward. We must load the asset
fraction. The most important are value and timescale, which represent the (AVAsset), create the item to manage the asset (AVPlayerItem), add the item to
numerator and denominator, respectively. For example, if we want to the player (AVPlayer), and associate the player to a layer to display the
create a CMTime structure to represent 0.5 seconds, we may declare 1 as media on the screen (AVPlayerLayer). But playing the media requires an
the numerator and 2 as the denominator (1 divided by 2 is equal to 0.5). additional step. The media does not become immediately available and
The class includes initializers and type properties to create these values. therefore, we cannot play it right away; we must wait until enough frames
The following are the most frequently used. have been loaded for the media to be ready to play. The status is reported
by the status property of the AVPlayerItem object, so we must add an observer
CMTime(value: CMTimeValue, timescale: CMTimeScale)—This for this property to start playing the media only after the property returns
the value readyToPlay (see KVO in Chapter 16)
initializer creates a CMTime structure with the values specified by the
The following code shows how to build a video player. The example
value and timescale arguments. The arguments are integers of type
assumes that we have added a file called trailer.mp4 to the project and
Int64 and Int32, respectively.
have an interface with a single scene that includes an empty view pinned
CMTime(seconds: Double, preferredTimescale: CMTimeScale) to the edges.
—This initializer creates a CMTime structure from a floating-point value
that represents the seconds and a timescale. The seconds argument Listing 18-16: Building a video player
determines the seconds we want to assign to the structure, and the 

preferredTimescale argument determines the scale we want to use. A import UIKit


import AVFoundation
we want to show the video, but the video does not start until the value of
class ViewController: UIViewController {
@IBOutlet weak var videoView: UIView! the status property is readyToPlay.
var playerItem: AVPlayerItem! The size of the layer for the player is defined according to the size of the
var player: AVPlayer!
var playerLayer: AVPlayerLayer!
view (playerLayer.frame = view.bounds). Because this value is not determined by
constraints, it must be updated every time the device is rotated. This is
override func viewDidLoad() { why at the end of the view controller we added the viewWillTransition()
super.viewDidLoad()
let bundle = Bundle.main method. Before rotating the interface, the system calls this method to
let videoURL = bundle.url(forResource: "trailer", withExtension: "mp4") report the size that the main view is going to adopt at the end of the
let asset = AVAsset(url: videoURL!)
rotation. By changing the layer’s size to this value, we adapt the video to
playerItem = AVPlayerItem(asset: asset) any orientation.
playerItem.addObserver(self, forKeyPath: "status", options: [], context: nil)

player = AVPlayer(playerItem: playerItem) Do It Yourself: Create a project. Add an empty view to the scene.
playerLayer = AVPlayerLayer(player: player) Connect the view to the ViewController class with an Outlet called
playerLayer.frame = view.bounds videoView. Complete the class with the code in Listing 18-16.
let layer = videoView.layer
layer.addSublayer(playerLayer)
Download the file trailer.mp4 from our website and add it to your
} project (remember to mark the option Add to Target). Run the
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change:
[NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
application. The video should start playing as soon as the application
if keyPath == "status" { is launched.
if playerItem.status == .readyToPlay {
playerItem.removeObserver(self, forKeyPath: "status")
player.play() The previous example plays the video, but it does not provide any tools for
} the user to control the process. The AVPlayer class includes methods to play,
}
} pause, and check the state of the media, but we are responsible for
override func viewWillTransition(to size: CGSize, with coordinator: creating the interface. Figure 18-12, below, introduces a new interface with
UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator) the view to show the video and an additional view in front of it with a
playerLayer.frame.size = size button and a Progress View to allow the user to play the video and see the
}
}
progression over time.

Figure 18-12: Controls for a custom video player
In this example, we load the video from the bundle and create the player
structure as soon as the scene is loaded. The player is associated with an
AVPlayerLayer layer and the layer is added as a sublayer of the view where

override func viewDidLoad() {


super.viewDidLoad()
playButton.isEnabled = false
let bundle = Bundle.main
let videoURL = bundle.url(forResource: "trailer", withExtension: "mp4")

let asset = AVAsset(url: videoURL!)


playerItem = AVPlayerItem(asset: asset)
playerItem.addObserver(self, forKeyPath: "status", options: [], context: nil)
player = AVPlayer(playerItem: playerItem)
playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = view.bounds
let layer = videoView.layer
layer.addSublayer(playerLayer)
}
}
 

How we control the process and respond to the interface depends on the In Listing 18-17, we add a few more Outlets to the ViewController class to
requirements of our application. For this example, we have decided to reference the elements of the interface and disable the Play button until
define two properties to keep track of the state of the media, ready and the video is ready to play, but the code to prepare the player remains the
playing. The ready property will be true when the media is ready to play and
same. The difference is in how the video is played because this time we
the playing property will be true while the media is being played. The need to wait until the user presses the Play button. This change appears in
following are the definition of the properties and the initial configuration the observeValue() method. Instead of playing the video when the value of
required by our application. the status property changes to readyToPlay, we assign the value true to the
ready property to let the rest of the code know that the video is ready to be

Listing 18-17: Preparing the video player played. But this is not all the method has to do. We must also register an

observer to keep the progress bar updated while the video is being played.
import UIKit
The AV Foundation framework offers the addPeriodicTimeObserver() method to
import AVFoundation create an observer. The method requires a CMTime value to determine the
frequency in which the code will be executed, a reference to the main
class ViewController: UIViewController {
@IBOutlet weak var videoView: UIView! queue (the Main Actor), and a closure with the code we want to execute
@IBOutlet weak var playButton: UIButton! every time the observer is triggered.
@IBOutlet weak var progressBar: UIProgressView!
var playerItem: AVPlayerItem!
var player: AVPlayer! Listing 18-18: Updating the progress bar
var playerLayer: AVPlayerLayer!

var ready = false
var playing = false override func observeValue(forKeyPath keyPath: String?, of object: Any?, change:
[NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "status" { The player and the progress bar are ready, so the only thing left is to add
if playerItem.status == .readyToPlay {
ready = true the Action for the Play button. This is a simple method that executes the
playButton.isEnabled = true player’s play() or pause() methods depending on the value of the playing
playerItem.removeObserver(self, forKeyPath: "status")
property.
let interval = CMTime(value: 1, timescale: 2)
player.addPeriodicTimeObserver(forInterval: interval, queue: .main, using: { [unowned Listing 18-19: Playing and pausing the video
self] time in
let duration = self.playerItem.duration 
let position = time.seconds / duration.seconds @IBAction func playVideo(_ sender: UIButton) {
self.progressBar.progress = Float(position) if ready {
}) if playing {
} player.pause()
} playing = false
}
 var current = playButton.configuration
current?.title = "Play"
playButton.configuration = current
In Listing 18-18, we create a CMTime value to represent a time of 0.5 } else {
seconds, and then use it in the call of the addPeriodicTimeObserver() method to player.play()
register the observer. After this, the closure provided to the observer will playing = true

be executed every 0.5 seconds during playback. In this closure, we get the var current = playButton.configuration
current time and the duration of the video in seconds and calculate the current?.title = "Pause"
playButton.configuration = current
right position of the progress bar by turning seconds into a value between }
0.0 and 1.0 (the minimum and maximum values of a Progress View by }
default). }

IMPORTANT: The addPeriodicTimeObserver() method doesn't work with Figure 18-13: Custom video player
Swift concurrency. Instead, it requires the thread to be defined by a © Copyright 2008, Blender Foundation / www.bigbuckbunny.org
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
this method runs in the main queue. Notice that this closure is
required to define self as unowned to ensure that the reference does
not create a strong reference cycle.

servers (only URLs starting with https://). In Chapter 19, we will learn
how to create URL structures to access remote documents and how
to configure the application to be able to work with unsecured URLs.

The addPeriodicTimeObserver() method allows us to update the controls, but


we can also listen to notifications to respond to changes in the state of the
player. The AVPlayerItem class defines several notifications to report events
that happened during playback. For example, we can listen to the
AVPlayerItemDidPlayToEndTime notification to know when the video has
finished.

Listing 18-20: Detecting when the video finishes playing
Do It Yourself: Add a view with a height of 45 points and pin it to 

the bottom and the sides of the main view to represent the control func checkEnding() async {
let center = NotificationCenter.default
bar for the video player. Change the color of the bar to gray. Click on let name = NSNotification.Name.AVPlayerItemDidPlayToEndTime
the color, select the Custom option to open the Color Picker, and for await _ in center.notifications(named: name, object: playerItem) {
await playerItem.seek(to: CMTime.zero)
declare an opacity of 50%. Include a button called Play and a
Progress View inside (Figure 18-12). Select the Progress View and await MainActor.run {
var current = self.playButton.configuration
assign the value 0 to it from the Attributes Inspector panel. Connect current?.title = "Play"
the button, and the Progress View with Outlets called playButton and self.playButton.configuration = current
self.playing = false
progressBar. Connect the Play button with an Action called playVideo(). }
}
Update the ViewController class with the codes in Listings 18-17, 18-18, }
and 18-19. Remember to add the viewWillTransition() method of Listing 
18-16 to adapt the layer to any orientation. Run the application and
press Play. This method creates a for in loop to listen to the AVPlayerItemDidPlayToEndTime
notification. Every time a video finishes playing, the player posts this
notification. Inside the loop, we call the seek() method to move the player
IMPORTANT: You cannot only play videos from files added to the
back to the beginning (time zero), and then reset the controls to allow the
project (local), but also from the Web (remote). All you need to do is
user to play the video again.
to create a URL structure with the URL pointing to the file you want
To start running this loop, we must call the checkEnding() method from an
to play and use it as the source for the asset. There is a restriction asynchronous task as soon as the scene is loaded. The following is the code
though. You can only play remote files that are stored in secure
we need to add to the viewDidLoad() method of our view controller to begin All we need to do to play a sequence of videos is to create the
this process. AVPlayerItem objects to load each video and create an AVQueuePlayer object to
replace the AVPlayer object we have used so far. The following example
Listing 18-21: Initiating the asynchronous task to listen to player introduces the necessary modifications to the ViewController class of Listing
notifications 18-17 to play two videos called videobeaches.mp4 and videotrees.mp4.

Task(priority: .high) { Listing 18-22: Playing a list of videos
await checkEnding()
} 
 import UIKit
import AVFoundation
Do It Yourself: Add the method in Listing 18-20 to the ViewController class ViewController: UIViewController {
class and the code in Listing 18-21 to the viewDidLoad() method. Run @IBOutlet weak var videoView: UIView!
@IBOutlet weak var playButton: UIButton!
the application, press the Play button, and let the video play to the @IBOutlet weak var progressBar: UIProgressView!
end. Press the Play button to play it again. var playerItem: AVPlayerItem!
var player: AVQueuePlayer!
var playerLayer: AVPlayerLayer!
If we want to play multiple videos in sequence, we could use this var ready = false
var playing = false
notification to assign a new asset to the AVPlayer object, but the framework
offers a subclass of the AVPlayer class called AVQueuePlayer designed override func viewDidLoad() {
super.viewDidLoad()
specifically for this purpose. The class creates a playlist from an array of playButton.isEnabled = false
AVPlayerItem objects. The following are the class initializer and some of its
let bundle = Bundle.main
methods. let videoURL1 = bundle.url(forResource: "videobeaches", withExtension: "mp4")
let asset1 = AVAsset(url: videoURL1!)
playerItem = AVPlayerItem(asset: asset1)
AVQueuePlayer(items: [AVPlayerItem])—This initializer creates a playerItem.addObserver(self, forKeyPath: "status", options: [], context: nil)
play list with the items specified by the items argument.
let videoURL2 = bundle.url(forResource: "videotrees", withExtension: "mp4")
advanceToNextItem()—This method plays the next item on the list. let asset2 = AVAsset(url: videoURL2!)
let playerItem2 = AVPlayerItem(asset: asset2)
insert(AVPlayerItem, after: AVPlayerItem?)—This method inserts
a new item to the list. player = AVQueuePlayer(items: [playerItem, playerItem2])

remove(AVPlayerItem)—This method removes an item from the playerLayer = AVPlayerLayer(player: player)


playerLayer.frame = view.bounds
list. let layer = videoView.layer
layer.addSublayer(playerLayer)
}
}

 18.2 Color Picker


Do It Yourself: Update the code in the ViewController class of Listing 
18-17 with the code in Listing 18-22. Download the videos
videobeaches.mp4 and videotrees.mp4 from our website and add Along with the predefined controllers for the camera, video, and Photo
Library, UIKit includes a controller to allow the user to pick a color. It is
them to your project. Remember to include the methods in Listings
defined by the UIColorPickerViewController class. Once the object is created,
18-18 and 18-19, and also the viewWillTransition() method of Listing 18-
we can configure the picker with the following properties.
16. Run the application. The videos should be played one after
another. selectedColor—This property sets or returns the selected color. It is
of type UIColor.
supportsAlpha—This property sets or returns a Boolean value that
determines whether the user is allowed to select the color's alpha
level.

The framework also includes the UIColorPickerViewControllerDelegate protocol


with the following methods to respond to actions performed by the user
on the picker.

colorPickerViewControllerDidFinish(UIColorPickerViewContro
ller)—This method is called on the delegate when the user closes the
picker.
colorPickerViewController(UIColorPickerViewController,
didSelect: UIColor, continuously: Bool)—This method is called on
the delegate when the user selects a color.

The following example illustrates how to set up a color picker, configure its
values, and get the color selected by the user. The code assumes that we
have a button on the interface connected to an Action in the ViewController
class called openColorPicker().

Listing 18-23: Presenting a color picker


 controller. The result is shown below.
import UIKit
Figure 18-14: Color picker
class ViewController: UIViewController, UIColorPickerViewControllerDelegate {
var picker: UIColorPickerViewController!
var selected: UIColor!

override func viewDidLoad() {


super.viewDidLoad()
picker = UIColorPickerViewController()
picker.delegate = self
}
@IBAction func openColorPicker(_ sender: UIButton) {
if let color = selected {
picker.selectedColor = color 
}
present(picker, animated: true, completion: nil)
}
Do It Yourself: Create a new project. Add a button to the scene
func colorPickerViewController(_ viewController: UIColorPickerViewController, didSelect (Figure 18-14, left). Connect the button to the ViewController class with
color: UIColor, continuously: Bool) {
selected = color an Action called openColorPicker(). Update the view controller with the
view.backgroundColor = selected code in Listing 18-23. Run the application, press the button, and
picker.dismiss(animated: true, completion: nil)
} select a color. You should see something like Figure 18-14. If you
} want the user to be able to change the selection before closing the

picker, remove the call to the dismiss() method.
The view controller in Listing 18-23 conforms to the
UIColorPickerViewControllerDelegate protocol and defines two properties: the
picker property to store a reference to the controller, and the selected
property to store the last color selected by the user. When the main view is
loaded, we create the controller and assign the ViewController object as its
delegate. The view controller also includes an Action connected to a button
on the interface. When the button is pressed, the code in this method
checks whether a color was already selected and assigns it to the picker's
selectedColor property, so the picker always shows the last color selected by
the user. Finally, the color picker controller is presented on the screen, as
we did before for other controllers.
In the delegate method, we get the color selected by the user from the
selectedColor property, assign it to the view's background, and dismiss the

CHAPTER 19 - WEB 19.1 Links


The most important aspect of the Web is the ease with which we can
access documents with a simple link. A link is a short text or an image that
is associated with a URL that determines the location of a document. When
the user clicks or taps the text or image representing the link, the
document is opened. Links were designed for the Web, but we can add
them to our applications and let the system decide where to open the
document. For instance, if the link contains a web address, the system
opens the browser to load the document.
Web addresses are created from URL structures. We have used these types
of structures before to determine the location of files, but we can also use
them to access remote documents. The class includes the following
initializers to create a URL.

URL(string: String)—This initializer creates a URL structure with the


URL specified by the string argument.
URL(string: String, relativeTo: URL?)—This initializer creates a
URL structure with the URL specified by the arguments. The URL is
created by adding the value of the string argument to the value of the
relativeTo argument. For example, if the value of the string argument
is "http://www.formasterminds.com" and the value of the relativeTo
argument is "index.php", the URL structure will contain the URL
"http://www.formasterminds.com/index.php".
URL(dataRepresentation: Data, relativeTo: URL?, isAbsolute:
Bool)—This initializer creates a URL structure with the URL specified
by the arguments. The URL is created by adding the value of the
dataRepresentation argument to the value of the relativeTo
argument. The isAbsolute argument is a Boolean value that determines
if the URL is absolute or not (it includes all the information required to }
}
access the resource). 

The URL structure is just a container for the location of the document we Do It Yourself: Create a new project. Add a button to the initial
want to open, but the document is opened from the Scene (the window). scene and connect it to the ViewController class with an Action called
The UIScene class offers the following method for this purpose. openWeb(). Complete the ViewController class with the code in Listing 19-
1. Run the application and press the button. The system should open
open(URL, options: OpenExternalURLOptions?)—This the browser and load the website.
asynchronous method opens the URL specified by the first argument.
The options argument is an object that configures the operation In the last example, we have defined the URL in code, but sometimes the
(defined as nil to set standard options). The method returns a Boolean URL is provided by the user or taken from another document. In cases like
value that determines whether the document was opened or not. this, the URL may contain characters that are not allowed and can cause
the location to be impossible to identify. To make sure that the URL is valid,
The following example opens the website www.formasterminds.com when we must turn unsafe characters into percent-encoding characters. These
a button is pressed. The code creates the URL structure from a string, gets are characters represented by the % sign followed by a hexadecimal
a reference to the Scene managing the window from the windowScene number. Fortunately, there are methods that can correct unsafe characters
property provided by the UIWindow class, and finally calls the open() method in a string for us. We have studied a method like this provided by the String
on this Scene to open the URL. The system reads the URL, detects that it is structure in Chapter 15. The data(using: Encoding, allowLossyConversion: Bool)
a web address and opens the browser to load the website. method turns a string into a Data structure using a type of encoding that
automatically corrects the characters.
Listing 19-1: Opening a website
 Listing 19-2: Encoding URLs
import UIKit 
import UIKit
class ViewController: UIViewController {
@IBAction func openWeb(_ sender: UIButton) { class ViewController: UIViewController {
let web = "http://www.formasterminds.com" @IBAction func openWeb(_ sender: UIButton) {
if let webURL = URL(string: web) { let web = "http://www.formasterminds.com"
Task(priority: .high) { if let dataURL = web.data(using: String.Encoding.utf8, allowLossyConversion: false) {
await openURL(url: webURL) if let webURL = URL(dataRepresentation: dataURL, relativeTo: nil, isAbsolute: true) {
} Task(priority: .high) {
} await openURL(url: webURL)
} }
@MainActor func openURL(url: URL) async { }
let scene = view.window?.windowScene }
await scene?.open(url, options: nil) }

@MainActor func openURL(url: URL) async { 19.2 Safari View Controller


let scene = view.window?.windowScene
await scene?.open(url, options: nil) 
}
}
 Links provide access to the Web from our app, but they open the
document in an external application. Considering how important it is for
The utf8 format used in the code of Listing 19-2 works with ASCII characters applications to capture the user’s attention, Apple includes a framework
and therefore it is suitable for the creation of URLs. The data(using:) method called SafariServices. This framework allows us to incorporate the Safari
returns a Data structure containing the URL, so we create the URL with the browser into our app to offer a better experience to our users. The
initializer appropriate for this type of value. Once the URL structure is framework includes the SFSafariViewController class to create a scene to
created, the process to open the URL is the same. display web pages and tools for navigation.

SFSafariViewController(url: URL)—This initializer creates a new


Safari View Controller that automatically loads the website indicated
by the url argument.
SFSafariViewController(url: URL, configuration:
Configuration)—This initializer creates a new Safari View Controller
with the configuration specified by the configuration argument that
automatically loads the website indicated by the url argument. The
configuration argument is a property of an object of the Configuration
class included in the SFSafariViewController class. The properties
available are entersReaderIfAvailable and barCollapsingEnabled.

To include a Safari View Controller in our app, we must create the


controller with one of the initializers and present it with the present()
method, as we did for previous controllers. The following example creates
a Safari View Controller to load the website www.formasterminds.com.
The code assumes that we have included a button in the main view
connected to an Action called openWeb(), as we did for the previous
examples.

Listing 19-3: Loading a website with a Safari View Controller


 Listing 19-4: Configuring the scene
import UIKit 
import SafariServices import UIKit
import SafariServices
class ViewController: UIViewController {
@IBAction func openWeb(_ sender: UIButton) { class ViewController: UIViewController {
let url = URL(string: "http://www.formasterminds.com") @IBAction func openWeb(_ sender: UIButton) {
let controller = SFSafariViewController(url: url!) let url = URL(string: "http://www.formasterminds.com")
present(controller, animated: true, completion: nil)
} let controller = SFSafariViewController(url: url!)
} controller.dismissButtonStyle = .close
 controller.preferredBarTintColor = UIColor(red: 81/255, green: 91/255, blue: 119/255, alpha:
1.0)
Do It Yourself: Update the ViewController class with the code in controller.preferredControlTintColor = UIColor.white

Listing 19-3. Run the application and press the button. The system present(controller, animated: true, completion: nil)
opens a scene with a browser and all the tools required for }
}
navigation, including a Done button to close it. 

The SFSafariViewController class also offers the following properties for The code in this example also modifies the dismissButtonStyle property to
configuration. change the type of button shown by the browser to close the scene from
Done to Close.
dismissButtonStyle—This property sets or returns a value that
determines the type of button the view controller is going to show to Figure 19-1: Custom Safari View Controller
dismiss the scene. It is an enumeration of type DismissButtonStyle with
the values done (default), close, and cancel.
preferredBarTintColor—This property sets or returns a UIColor
value that determines the color of the bars.
preferredControlTintColor—This property sets or returns a UIColor
value that determines the color of the controls.

The following example takes advantage of these properties to get the
colors of the bars to match the colors used by the When the user scrolls the page, the controller collapses the bars to make
www.formasterminds.com website. more room for the content. This makes difficult for the user to dismiss the
scene or use the tools. If we think that it would be better for our app to

always keep the bars visible and at their original size, we can initialize the safariViewControllerDidFinish(SFSafariViewController)—This
controller with a configuration object and assign the value false to the method is called by the controller when the scene is dismissed (the
barCollapsingEnabled property. user pressed the Done button).
The Configuration class provides a simple initializer with no parameters. Once
the object is created, we can configure its properties and assign it to the The Safari View Controller includes the delegate property to define a
Safari View Controller from the controller’s initializer, as shown in the delegate. The following example assigns the ViewController class as the
following example. delegate and implements the safariViewControllerDidFinish() method to
deactivate the button on the interface when the user dismisses the scene
Listing 19-5: Displaying the bars in their original size (the user is only able to open the scene once).

import UIKit Listing 19-6: Assigning a delegate to the Safari View Controller
import SafariServices

class ViewController: UIViewController { import UIKit
@IBAction func openWeb(_ sender: UIButton) { import SafariServices
let url = URL(string: "http://www.formasterminds.com")
class ViewController: UIViewController, SFSafariViewControllerDelegate {
let config = SFSafariViewController.Configuration()
@IBOutlet weak var openButton: UIButton!
config.barCollapsingEnabled = false
let controller = SFSafariViewController(url: url!, configuration: config) @IBAction func openWeb(_ sender: UIButton) {
present(controller, animated: true, completion: nil) let url = URL(string: "http://www.formasterminds.com")
} let controller = SFSafariViewController(url: url!)
} controller.delegate = self
 present(controller, animated: true, completion: nil)
}
Do It Yourself: Update the ViewController class with the code in func safariViewControllerDidFinish(_ controller: SFSafariViewController){
openButton.isEnabled = false
Listing 19-5. Run the application and scroll the page. The bars should }
stay at the same size and the buttons should always be visible. }

The framework also defines the SFSafariViewControllerDelegate protocol, so we


Do It Yourself: Update the ViewController class with the code in
can assign a delegate to the Safari View Controller to control the process.
Listing 19-6. Connect the button in the scene with the openButton
The following are some of the methods defined by this protocol.
Outlet. Run the application, press the button, and press Done to
safariViewController(SFSafariViewController, close the Safari View Controller. The button should be disabled.
didCompleteInitialLoad: Bool)—This method is called by the
controller when the initial website finish loading.
19.3 WebKit Framework isLoading—This property returns a Boolean value that indicates if
the view is in the process of loading a URL.

canGoBack—This property returns a Boolean value that indicates if
Including a Safari web browser in our interface is a good way to keep the the view can navigate to the previews page.
users inside our app, but in some cases this option is not customizable canGoForward—This property returns a Boolean value that
enough. To provide more alternatives, Apple offers the WebKit framework. indicates if the view can navigate to the next page.
With this framework we can display web content within a view. The class
provided for this purpose is a subclass of UIView called WKWebView. estimatedProgress—This property returns a value of type Double
between 0.0 and 1.0 that indicates the fraction of the content that
WKWebView(frame: CGRect, configuration: has been already loaded.
WKWebViewConfiguration)—This initializer creates a WKWebView load(URLRequest)—This method loads the content of a URL. The
object with the size determined by the frame argument and the argument is the request for the URL we want to open.
configuration set by the configuration argument. goBack()—This method navigates to the previous page on the list.
goForward()—This method navigates to the next page on the list.
The Library also includes an option to add a WebKit View to a scene in the
Storyboard. go(to: WKBackForwardListItem)—This method navigates to the
page indicated by the argument. The to argument is an object that
Figure 19-2: WebKit View option in the Library represents a page in the navigation list.
reload()—This method reloads the current page (it refreshes the
page).
 stopLoading()—This method stops the view from loading the
content.
The following are some of the properties and methods defined in the
WKWebView class to load and manage the content. URLs are addresses that indicate the location of a file, but they also
establish the kind of protocol we are going to use to set the connection.
title—This property returns a string with the title of the current page. The UIKit framework offers the URLRequest structure to manage this
url—This property returns a URL structure with the URL of the information. The structure includes the following initializers.
current page.
URLRequest(url: URL, cachePolicy: CachePolicy,
customUserAgent—This property sets or returns a string with the timeoutInterval: TimeInterval)—This initializer creates a
name of the user agent (nil by default).
URLRequest structure for the URL and protocol specified by the url

argument (the information of the protocol is taken from the URL delegate when an error occurs.
itself). The cachePolicy argument is an enumeration that determines webView(WKWebView,
how the request will work with the cache. The possible values are: didReceiveServerRedirectForProvisionalNavigation:
useProtocolCachePolicy (default), reloadIgnoringLocalCacheData, WKNavigation!)—This method is called on the delegate when the
reloadIgnoringLocalAndRemoteCacheData, reloadIgnoringCacheData, server redirects the navigator to a different destination.
returnCacheDataElseLoad, returnCacheDataDontLoad, and
reloadRevalidatingCacheData. The timeoutInterval argument is the The process to load a website in a WebKit View is simple. We get the URL,
maximum time allowed for the system to process the request (60.0 create a request, and ask the view to load it.
seconds by default).
Listing 19-7: Loading a website with a WebKit View
A WebKit View can report the state of the content to a delegate. For this 
purpose, the framework defines the WKNavigationDelegate protocol. The import UIKit
import WebKit
following are some of its methods.
class ViewController: UIViewController {
webView(WKWebView, decidePolicyFor: @IBOutlet weak var webView: WKWebView!
WKNavigationAction, decisionHandler: Block)—This method is
override func viewDidLoad() {
called on the delegate to determine if the view should process a super.viewDidLoad()
request. The decidePolicyFor argument is an object with information
if let webURL = URL(string: "https://www.google.com") {
about the request, and the decisionHandler argument is a closure let request = URLRequest(url: webURL)
that we must execute to communicate our decision to the system. The webView.load(request)
}
closure takes a value of type WKNavigationActionPolicy, an enumeration }
with the properties cancel and allow. }

webView(WKWebView, didStartProvisionalNavigation:
WKNavigation!)—This method is called on the delegate when the This example assumes that we have added a WebKit View to the interface
view begins loading new content. from the Library, and it is connected to the ViewController class with an
webView(WKWebView, didFinish: WKNavigation!)—This Outlet called webView. To prepare the request, we get the URL structure
method is called on the delegate when the view finishes loading the with the address we want to access and initialize the URLRequest structure
content. with values by default (only the URL is required). Finally, the request is
loaded with the load() method and the website is shown on the screen.
webView(WKWebView, didFailProvisionalNavigation:
WKNavigation!, withError: Error)—This method is called on the
Do It Yourself: Create a project. Add a WebKit View to the scene. In this interface, we have embedded the scene in a Navigation Controller
Connect the WebKit View to the ViewController class with an Outlet and added three buttons to the Navigation Bar to let the user go back,
called webView. Complete the ViewController class with the code in move forward, and refresh the page.
Listing 19-7. Run the application. You should see Google’s website on Before modifying the content of a WebKit View we need to know what we
the screen. can and cannot do. For example, we can only go back if the user has
already navigated forward. This is when the delegate methods become
useful. Implementing the protocol methods, we can check the status of the
With this process, we can load any website we want, including those
content every time a new document is loaded, as shown next.
specified or selected by the user (we just need to prepare the URL the way
we did before for links in Listing 19-2). But users need more control over
Listing 19-8: Implementing a custom web browser
the content. For instance, if we use the previous example to perform a

Google search, we will notice right away that there is no way to go back to
import UIKit
the previous page or navigate back to the beginning. WebKit Views offer import WebKit
several methods to manipulate their content, and delegate methods to
respond to changes, but we must provide the tools for navigation, as class ViewController: UIViewController, WKNavigationDelegate {
@IBOutlet weak var backButton: UIBarButtonItem!
shown in the following example. @IBOutlet weak var forwardButton: UIBarButtonItem!
@IBOutlet weak var refreshButton: UIBarButtonItem!
@IBOutlet weak var webView: WKWebView!
Figure 19-3: Custom web browser for our application
override func viewDidLoad() {
super.viewDidLoad()
updateButtons()

webView.navigationDelegate = self
if let webURL = URL(string: "https://www.google.com") {
let request = URLRequest(url: webURL)
webView.load(request)
}
}
@IBAction func moveBack(_ sender: UIBarButtonItem) {
webView.goBack()
}
@IBAction func moveForward(_ sender: UIBarButtonItem) {
webView.goForward()
}
@IBAction func refresh(_ sender: UIBarButtonItem) {
webView.reload()
 }
func webView(_ webView: WKWebView, decidePolicyFor navigationAction:
WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if webView.isLoading {

updateButtons() results. The buttons in the Navigation Bar should be enabled and
}
decisionHandler(.allow) disabled according to your location in the browsing history.
}
func updateButtons() {
backButton.isEnabled = webView.canGoBack
forwardButton.isEnabled = webView.canGoForward
}
}

The WKWebView class includes the navigationDelegate property to designate a


delegate for the view. In Listing 19-8, we assign the view controller as the
delegate and then implement the webView(WKWebView, decidePolicyFor:,
decisionHandler:) method to modify the interface every time a new page is
loaded. This method is called by the view to ask permission before
navigating to new content and therefore is perfect to control the buttons
on the interface. The method may be called multiple times per action, so
we check whether the view is loading new content with the isLoading
method, and then call our updateButtons() method to update the buttons.
This method considers the values returned by the canGoBack and
canGoForward properties to enable or disable the buttons, so the buttons to
navigate back and forward are only enabled when there are documents to
go to.

Do It Yourself: Create a project. Embed the initial scene in a


Navigation Controller. Add a WebKit View to the scene and pin it to
the sides of the main view (below the Navigation Bar). Connect the
WebKit View with an Outlet called webView. Add three Bar Buttons to
the Navigation Bar (you can change the appearance of the buttons
from the System Item option in the Attributes Inspector panel).
Connect the buttons to Outlets called backButton, forwardButton, and
refreshButton, and to Actions called moveBack(), moveForward(), and
refresh(). Complete the ViewController class with the code in Listing 19-8.
Run the application and search for a word. Click on a link in the
App Transport Security

In the latest examples, we open secure URLs (URLs that begin with the 
prefix https://), because these are the URLs allowed by default. iOS
implements a system called App Transport Security (ATS) to block insecure The Allow Arbitrary Loads key takes a Boolean value specified with the
URLs, like those starting with the prefix http:// (without the s). If we need strings YES and NO (or 1 and 0, respectively). Setting this key to YES (1)
to load insecure URLs, such as http://www.formasterminds.com, we can allows any URL to be opened. If what we want is to allow only specific
configure our app from the info.plist file to circumvent this security domains, we must use the Exception Domains key and add to the key
measure for all the websites or specific domains. The option to configure additional items with the domains we want to include. These items in turn
the App Transport Security system is called "App Transport Security require at least three more items with the keys NSIncludesSubdomains
Settings", as shown below. (Boolean), NSTemporaryExceptionAllowsInsecureHTTPLoads (Boolean), and
NSTemporaryExceptionMinimumTLSVersion (String). For example, the following
Figure 19-4: Option to configure App Transport Security configuration allows documents from the formasterminds.com domain to
be opened.

Figure 19-6: App Transport Security configured to allow URLs from


 formasterminds.com

The interface includes a + button on the right side of the item to add values
to that item. In this case, the button has two functions: if the arrow on the
left is pointing to the item (closed), a new item is added to the main list,
but if the arrow is pointing down (expanded), the new item is added to the
item as a new value (we can click the arrow to close or expand the item).
Values added to an item are shown below the item with a little indentation
to reflect the hierarchy. The value we need to add to the App Transport 
Security Settings item to allow insecure URLs to be opened is called "Allow
Arbitrary Loads", as shown below.

Figure 19-5: App Transport Security configured to allow insecure URLs

19.4 Web Content defined in the URLSession class.



data(from: URL, delegate: URLSessionTaskDelegate?)—This
asynchronous method adds a task to the session to download the
The Safari View Controller and the WebKit views were designed to show
content to the user, but the capacity to integrate that content with our app data at the URL indicated by the from argument. The delegate
is limited. Sometimes all we need is to extract a piece of information from argument is the delegate object used by the task to report updates
a document or process the data instead of showing the entire content as it during the process. The method returns a tuple with two values: a
is. In cases like this, we must load the document in the background and Data structure with the data returned by the server and a URLResponse
analyze it to extract only what we need. Foundation includes a group of object with the status of the request.
classes to get content referenced by a URL. The main class is called download(from: URL, delegate: URLSessionTaskDelegate?)—
URLSession. This class creates a session that manages an HTTP connection to
This asynchronous method adds a task to the session to download the
obtain data and download or upload files. The following are the initializers
file at the URL indicated by the from argument. The delegate
and type property provided by the class to create the session.
argument is the delegate object used by the task to report updates
during the process. The method returns a tuple with two values: a
URLSession(configuration: URLSessionConfiguration)—This
URL structure that indicates the location of the downloaded file and a
initializer creates a new session with the configuration set by the
URLResponse object with the status of the request.
argument. The configuration argument is an object that specifies the
session's behavior. bytes(from: URL, delegate: URLSessionTaskDelegate?)—This
asynchronous method adds a task to the session to download a
URLSession(configuration: URLSessionConfiguration,
sequence of bytes from the URL indicated by the from argument. The
delegate: URLSessionDelegate?, delegateQueue:
delegate argument is the delegate object used by the task to report
OperationQueue?)—This initializer creates a new session with the
updates during the process. The method returns a tuple with two
configuration set by the arguments. The configuration argument is an
values: a AsyncBytes structure with an asynchronous sequence of bytes
object that specifies the session's behavior, the delegate argument is
and a URLResponse object with the status of the request.
a reference to the delegate object we want to assign to the session,
and the delegateQueue argument is the queue in which the delegate
The following are the methods defined by the class to upload data and
methods are going to be executed.
files.
shared—This type property returns a standard session with a
configuration by default that is suitable to perform basic requests. upload(for: URLRequest, from: Data, delegate:
URLSessionTaskDelegate?)—This asynchronous method adds a
The session sets up the connection, but it does not perform any tasks. To task to the session to upload the data indicated by the from
download or upload data we must implement the following methods
argument. The delegate argument is the delegate object used by the class ViewController: UIViewController {
override func viewDidLoad() {
task to report updates during the process. The method returns a tuple super.viewDidLoad()
with two values: a Data structure with the data returned by the server Task(priority: .high) {
await loadWebsite()
and a URLResponse object with the status of the request. }
}
upload(for: URLRequest, fromFile: URL, delegate: func loadWebsite() async {
URLSessionTaskDelegate?)—This asynchronous method adds a let session = URLSession.shared
let webURL = URL(string: "https://www.yahoo.com")
task to the session to upload the file in the URL indicated by the do {
fromFile argument. The delegate argument is the delegate object let (data, response) = try await session.data(from: webURL!)
if let resp = response as? HTTPURLResponse {
used by the task to report updates during the process. The method let status = resp.statusCode
returns a tuple with two values: a Data structure with the data if status == 200 {
let content = String(data: data, encoding: String.Encoding.ascii)
returned by the server and a URLResponse object with the status of the print(content!)
} else {
request.
print("Error: \(status)")
}
These methods are asynchronous. When they finish downloading or }
} catch {
uploading the data, they return the result. For example, if we use the data() print("Error: \(error)")
method to get data from a website, the value returned includes a value }
}
with the data and an object of type URLResponse with the status of the }
request. When we access a URL using the HTTP protocol, the response is 
represented by an object of type HTTPURLResponse (a subclass of
URLResponse). This class includes the statusCode property to return a code that To download data, all we need is a session and a URL. The data() method is
determines the status of the request. There are several codes available to asynchronous, so we place the code inside an asynchronous method and
determine things like the success of the request (200) or more drastic run a task that calls this method as soon as the scene is loaded. In this
situations like when the website has been moved to a different address example, we load the website at www.yahoo.com and print the content on
(301). If all we want is to make sure that the data was downloaded the console. Because we are just loading a single web page, the standard
correctly, we can check if the value of the statusCode property is equal to 200 session returned by the shared property is more than enough. When the
before processing anything. The following example shows how to perform task is finished, we turn the data into a string with the String() initializer and
a basic request. then print the string on the console.
A standard session like the one we used in this example comes with a
Listing 19-9: Loading a remote document configuration by default that is suitable for most situations, but a custom
 session requires its own configuration. To configure a session, Foundation
import UIKit provides a class called URLSessionConfiguration with type methods and

properties to set a specific configuration. The following is the type property await loadWebsite()
}
we can use to get a configuration object with values by default. }
func loadWebsite() async {
let config = URLSessionConfiguration.default
default—This property returns a URLSessionConfiguration object with config.waitsForConnectivity = true
default settings. let session = URLSession(configuration: config)
let webURL = URL(string: "https://www.yahoo.com")

Once we get a basic object, we can adapt it to the requirements of our do {


application. The following are some of the properties offered by the let (data, response) = try await session.data(from: webURL!)
if let resp = response as? HTTPURLResponse {
URLSessionConfiguration class to modify the configuration. let status = resp.statusCode
if status == 200 {
let content = String(data: data, encoding: String.Encoding.ascii)
allowsCellularAccess—This property sets or returns a Boolean print(content!)
value that determines if the connection should be made when the } else {
print("Error: \(status)")
device is connected to a cellular network. }
}
timeoutIntervalForRequest—This property sets or returns a } catch {
value (a typealias of Double) that determines the number of
TimeInterval print("Error: \(error)")
}
seconds the session should wait for a request to be answered. The }
value by default is 60. }

waitsForConnectivity—This property sets or returns a Boolean
value that determines if the session should wait to perform the Do It Yourself: Create a new project. Update the ViewController class
request until the device gets connected to the network. The value by with the codes in Listings 19-9 or 19-10. Run the application. You
default is false. should see the HTML code of Yahoo's website printed on the
console.
Working with custom sessions only requires us to change how the session
is initialized, but the rest of the code remains the same. In the previous example, we didn't implement the delegate argument of
the data() method. This argument is optional, but we can declare it if we
Listing 19-10: Instantiating a custom session need to respond to changes during the process. The framework defines the
 URLSessionTaskDelegate protocol to create this delegate object. The protocol
import UIKit defines several methods. The following are the most frequently used.
class ViewController: UIViewController {
override func viewDidLoad() { urlSession(URLSession, task: URLSessionTask, didReceive:
super.viewDidLoad()
Task(priority: .high) { URLAuthenticationChallenge, completionHandler: Closure)—
This method is called on the delegate when authentication is config.waitsForConnectivity = true
let session = URLSession(configuration: config)
requested by the server. Our implementation must call the let webURL = URL(string: "https://www.yahoo.com")
completion handler received by the method with two arguments that
do {
define the settings and credentials. let (data, response) = try await session.data(from: webURL!, delegate: self)
if let resp = response as? HTTPURLResponse {
urlSession(URLSession, task: URLSessionTask, let status = resp.statusCode
willPerformHTTPRedirection: HTTPURLResponse, if status == 200 {
let content = String(data: data, encoding: String.Encoding.ascii)
newRequest: URLRequest, completionHandler: Block)—This print(content!)
method is called on the delegate when the server redirected the } else {
print("Error: \(status)")
connection to another URL. Our implementation must call the }
completion handler received by the method with an argument that }
} catch {
defines the new request (the value of the newRequest argument) or print("Error: \(error)")
}
the value nil if we do not want to follow the redirection. }
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection
Some websites, like www.yahoo.com, send the user to a different address response: HTTPURLResponse, newRequest request: URLRequest) async -> URLRequest? {
return request
that contains a version of the website customized to the user’s location }
and preferences. This means that the URL we provide does not represent }

the final destination; the server does not return any data but instead
redirects the user to another document. In cases like this, we can define a
In this example, we define the ViewController class as the delegate and return
custom session with a delegate and then implement the method of the
the new request. Every time the server asks for a redirection, the protocol
URLSessionTaskDelegate protocol to determine what we want to do when the
method is called and the request with the new URL is executed. The
server is redirecting our application.
process repeats until the destination is reached and then the content of
that document is printed on the console.
Listing 19-11: Following a redirection

import UIKit

class ViewController: UIViewController, URLSessionTaskDelegate {


override func viewDidLoad() {
super.viewDidLoad()
Task(priority: .high) {
await loadWebsite()
}
}
func loadWebsite() async {
let config = URLSessionConfiguration.default

JSON decode(Type, from: Data)—This method returns a value of the


data type specified by the first argument with the information

contained by the from argument. The from argument is a Data
Normal websites return documents written in HTML code. This is the basic structure that contains the JSON data we want to decode.
language implemented by every website on the Internet. It is composed of encode(Value)—This method returns a JSON representation of the
predefined tags that take care of organizing the document’s content. For data provided by the argument.
example, the tags <title> and </title> enclose the title of the document, so
we can use string methods to find the location of the last character of the For a structure to be decodable, it must conform to the Codable protocol.
<title> tag and the first character of the </title> tag and then cut the text in This is a protocol that defines initializers and methods required to encode
the middle to extract the title. The problem with this approach is that most and decode the values of the properties (see Chapter 15). For example, the
tags in HTML have the same name, making it very difficult to identify the following is a JSON file that contains information about a book.
information we want to retrieve. Also, websites do not follow a predefined
structure and their code may change without a warning. If we create an Listing 19-12: JSON file
app that extracts information from the HTML code of a web page, it may 
not only be illegal, but it could also stop working before the app is {
submitted to the App Store. This is not only a problem for mobile "title": "The Shining",
"author": "Stephen King"
applications but also for web applications in general. The solution was
}
found a long time ago with the creation of formatting languages that have 
the sole purpose of sharing data on the Web. Some are considered
programming languages and others are just format specifications, like To convert this data to a value we can process in Swift, we must define a
JSON. structure that conforms to the Codable protocol and includes all the
JSON (Javascript Object Notation) defines a dictionary-like notation to properties required to represent the values we want to read.
identify data. Every piece of data is stored with a key/value pair and related
values are enclosed in braces. The advantage of this format is that the Listing 19-13: Swift structure to decode JSON data
information is easy to find. Every value has a unique key. 
Because of the format, JSON files can easily be converted into Swift struct Book: Codable {
structures that we can process as any other structure in our code. let title: String
Foundation includes the JSONDecoder class to decode JSON data into Swift let author: String
}
structures and the JSONEncoder class to encode Swift structures into JSON 
data. These classes include their respective methods to decode and encode
the values. Because the Book structure conforms to the Codable protocol, it is ready for
coding and decoding, so we can use it to read the JSON file of Listing 19-12.
The following view controller demonstrates how to turn that JSON data 19-14. Run the application. You should see the values in the JSON file
into a Book structure and read its values. printed on the console.

Listing 19-14: Decoding JSON data The previous example explains how to read and decode a JSON file stored
 in the bundle, but this information is usually downloaded from online
import UIKit services. The JSON documents provided by these services are dynamically
class ViewController: UIViewController { generated and contain only the information requested by the application.
override func viewDidLoad() { For instance, the website www.openweathermap.org offers a service that
super.viewDidLoad()
let bundle = Bundle.main
generates JSON documents with information about the weather for
let jsonURL = bundle.url(forResource: "books", withExtension: "json") specific locations (https://openweathermap.org/api).
let jsonData = FileManager.default.contents(atPath: jsonURL!.path) To illustrate how to access and process the documents produced by these
let decoder = JSONDecoder() services, we are going to read posts from a website called JSONPlaceholder
do { that generates phony documents. The process doesn't introduce anything
let info = try decoder.decode(Book.self, from: jsonData!)
print("Title: \(info.title)")
new. We must load the document with a URLSession and then decode it with
print("Author: \(info.author)") a JSONDecoder object, as we did before.
} catch { The document we are going to download for this example is located at
print("Error: \(error)")
} https://jsonplaceholder.typicode.com/posts. The JSON code generated by
} this document includes multiple items, each with four values called userId,
}
 id, title, and body. The following model includes the structure we need to
decode these values.
This example assumes that we have created a file called books.json with
the JSON code of Listing 19-12. Once we read the file from the bundle and Listing 19-15: Defining a model to decode posts
get its content, we create a JSONDecoder object and then call the decode() 
method on it to turn the data into a Book structure. The values of this import UIKit
structure are finally printed on the console to confirm that the process was enum Sections {
successful. case main
}
struct Post: Codable, Identifiable {
Do It Yourself: Create a new project. Create a text file called var id: Int
var userId: Int
books.json with the JSON code of Listing 19-12 and add it to the
var title: String
project. Create a Swift file called Book.swift with the structure in var body: String
}
Listing 19-13. Update the ViewController class with the code in Listing
class ApplicationData {
var listPosts: [Post] = []
var dataSource: UITableViewDiffableDataSource<Sections, Post.ID>!

} AppData.dataSource = UITableViewDiffableDataSource<Sections, Post.ID>(tableView:


var AppData = ApplicationData() tableView) { tableView, indexPath, itemID in
 let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
if let item = AppData.listPosts.first(where: { $0.id == itemID }) {
var config = cell.defaultContentConfiguration()
We made the Post structure conform to the Identifiable protocol to list the config.text = item.title
values on a Table View (the Int data type is Hashable). The following is the config.secondaryText = item.body
cell.contentConfiguration = config
view controller for this table.
}
return cell
Listing 19-16: Decoding JSON data from the Web }
}
 func prepareSnapshot() {
import UIKit var snapshot = NSDiffableDataSourceSnapshot<Sections, Post.ID>()
snapshot.appendSections([.main])
class PostsViewController: UITableViewController { snapshot.appendItems(AppData.listPosts.map { $0.id })
override func viewDidLoad() { AppData.dataSource.apply(snapshot)
super.viewDidLoad() }
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "myCell") }
prepareDataSource() 

Task(priority: .high) {
await loadWebsite()
This example assumes that the initial scene in the interface has been
} replaced by a Table View Controller. When the scene is loaded, we create
} an asynchronous task to download the JSON file at the URL
func loadWebsite() async {
let session = URLSession.shared https://jsonplaceholder.typicode.com/posts. If the status of the response is
let webURL = URL(string: "https://jsonplaceholder.typicode.com/posts") 200, which means the data has been downloaded successfully, we decode
do {
let (data, response) = try await session.data(from: webURL!)
it into an array of Post structures, assign the array to the listPosts property in
if let resp = response as? HTTPURLResponse { the model, and update the snapshot to display the values on the screen.
let status = resp.statusCode The result is shown below.
if status == 200 {
let decoder = JSONDecoder()
AppData.listPosts = (try? decoder.decode([Post].self, from: data)) ?? [] Figure 19-7: List of posts downloaded from the Web
await MainActor.run {
self.prepareSnapshot()
}
}
}
} catch {
print("Error: \(error)")
}
}
func prepareDataSource() {

Do It Yourself: Create a new project. Replace the initial scene with


a Table View Controller. Create a subclass of UITableViewController with
the name PostsViewController and assign it to the new scene. Update
this class with the code in Listing 19-16. Create a Swift file called
ApplicationData.swift for the model in Listing 19-15. Run the
application. You should see the list of posts on the screen.

IMPORTANT: The keys used in a JSON document are selected by


the developer. Every developer decides what keys to use and how to
structure the information. To know the keys used to construct the
hierarchy, you need to load the JSON document in your browser or
find examples on the website.

CHAPTER 20 - MAP KIT 20.1 Map Kit View


Often, users need to visualize their location or the places they want to go
on a map to position themselves in the world. For these kinds of
applications, Apple offers the MapKit framework. The framework includes
all the tools necessary to create and configure maps with which users can
interact to find places and obtain information.
Maps are presented on the screen with a view created from a subclass of
UIView called MKMapView. The view takes care of loading the map, managing
user interaction, and displaying custom content generated by our
application.

Figure 20-1: Map Kit View option in the Library

Like a regular view, a Map Kit View can be pinned to the sides of the main
view or other elements. Figure 20-2 introduces the interface we are going
to use for the following examples. The scene includes a Map Kit View and a
bar button to interact with the map.

Figure 20-2: Interface with a Map Kit View


Configuring the Map

A Map Kit View can show the map in different styles, and let the user
zoom, pan, or rotate the map to find a location. The MKMapView class
includes properties to configure these features.

mapType—This property sets or returns a value that determines the


style of the map. It is an enumeration of type MKMapType with the
values standard (default), mutedStandard, satellite, hybrid, satelliteFlyover, and
hybridFlyover.

 isZoomEnabled—This property sets or returns a Boolean value that


determines if the user can zoom the map (true by default).
Do It Yourself: Create a new project. Embed the scene in a isScrollEnabled—This property sets or returns a Boolean value that
Navigation Controller. Add a Map Kit View to the scene and pin it to
determines if the user can scroll the map (true by default).
the Safe Area. Add a bar button called Show, as in Figure 20-2. We
isRotateEnabled—This property sets or returns a Boolean value
will use this button later to select locations. Run the application. You
that determines if the user can rotate the map (true by default).
should see the map of the world on the screen.
isPitchEnabled—This property sets or returns a Boolean value that
determines if the camera's pitch angle is considered when rendering
the map (true by default).

To modify the map, we must import the MapKit framework and then
change the values of these properties. The following example defines the
style of the map and disables rotation.

Listing 20-1: Configuring the map



import UIKit
import MapKit

class ViewController: UIViewController {


@IBOutlet weak var mapView: MKMapView!

typealias of Double).
override func viewDidLoad() {
super.viewDidLoad() altitude—This property returns the altitude of the location in
mapView.mapType = .satellite
mapView.isRotateEnabled = false meters. It is of type CLLocationDistance (a typealias of Double).
}
}
 The CLLocation class also includes a practical method to calculate the
distance between two CLLocation objects.
Do It Yourself: Connect the Map Kit View to the ViewController class
with an Outlet called mapView. Complete the class with the code in distance(from: CLLocation)—This method returns a value that
Listing 20-1 and run the application. You should see a satellite map determines the distance between the location in the CLLocation object
of the world. and the location determined by the from argument. The argument is
a CLLocation object with the location we want to compare. The value
Users can zoom in and out to find a specific place on the map, but we can returned is of type CLLocationDistance (a typealias of Double).
also do it from code. To determine locations in the map, the MapKit
framework implements values defined in a framework called Core These values define the location, but the map is scrolled to those locations
Location. The most important class is CLLocation, designed to store by properties and methods of the MKMapView class. The following are the
information about a location. The class includes an initializer to create the ones available to get the values of the area currently displayed on the
object and properties to retrieve the values. screen or set a new one.

CLLocation(latitude: CLLocationDegrees, longitude: region—This property returns an MKCoordinateRegion structure with


CLLocationDegrees)—This initializer creates a CLLocation object with values that determine the area currently displayed on the screen.
the location determined by the arguments. The CLLocationDegrees data centerCoordinate—This property returns a CLLocationCoordinate2D
type is a typealias of Double. structure with the coordinates at the center of the visible area.
coordinate—This property returns the location's coordinates. It is a setRegion(MKCoordinateRegion, animated: Bool)—This
structure of type CLLocationCoordinate2D with the latitude and longitude method sets the map’s visible region. The first argument is a structure
properties. that contains a CLLocationCoordinate2D value to determine the region’s
horizontalAccuracy—This property returns a value that determines center coordinates, and a value of type MKCoordinateSpan to determine
the accuracy of the location’s latitude and longitude. It is of type the region’s size in degrees. The animated argument determines if the
CLLocationAccuracy (a typealias of Double). transition to the region will be animated.
verticalAccuracy—This property returns a value that determines setCenter(CLLocationCoordinate2D, animated: Bool)—This
the accuracy of the location’s altitude. It is of type CLLocationAccuracy (a method sets the region’s coordinates. The first argument specifies the
latitude and longitude, and the animated argument determines if the }

transition to the location will be animated.
This code creates a CLLocation object with the coordinates of the Apple
The MKCoordinateRegion structure is created from a CLLocationCoordinate2D Store, defines a region around that area, and finally sets the map’s visible
structure that contains the properties latitude and longitude to return the region with the setRegion() method. This time, instead of the entire world,
latitude and longitude of the location, and an MKCoordinateSpan structure the Map Kit View shows an area of New York City.
that contains the size of the region in degrees. Because the degrees of an
area can be difficult to calculate, the framework defines an initializer for Figure 20-3: Map showing a specific region of New York City
this structure to set the size of the region in meters.

MKCoordinateRegion(center: CLLocationCoordinate2D,
latitudinalMeters: CLLocationDistance, longitudinalMeters:
CLLocationDistance)—This initializer creates an MKCoordinateRegion
structure with the values determined by its arguments. The first
argument specifies the region’s coordinates, and the second and third
arguments determine the vertical and horizontal size of the region in
meters.

The following example implements this structure to show an area of 1000


meters around the location of one of the Apple Stores in New York.

Listing 20-2: Displaying a region



import UIKit
import MapKit

class ViewController: UIViewController {


@IBOutlet weak var mapView: MKMapView!

override func viewDidLoad() { 


super.viewDidLoad()
let location = CLLocation(latitude: 40.7637825011971, longitude: -73.9731328627541)
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000, Do It Yourself: Update the ViewController class with the code in
longitudinalMeters: 1000) Listing 20-2. Run the application. You should see an area of New York
mapView.setRegion(region, animated: false)
} City with the Apple Store at the center.

Annotations

The previous example sets the visible area around the Apple Store.
Because this is a relevant location, the map shows an icon with the name
of the store, but this is not always the case. Most locations do not show
any reference at all, and the user has to guess where the exact location
actually is. To add graphics to the map to mark a location, we use
annotations.
Annotations provide additional information of a particular location. They
are associated with a view that can display an image to represent the
annotation, such as a pin, the title and subtitle of the location, and a
subview, called Callout, to show additional information. Figure 20-4, below,
illustrates how an annotation looks like on the map.

Figure 20-4: Annotation on the map

What the framework describes as annotations are objects that define the
basic aspects of an annotation, like the coordinates in which the
annotation will appear, the image that will represent the location, and the
title and subtitle to show along with it. The MapKit framework includes the
MKAnnotation protocol to create these objects. The protocol defines the
following properties.

coordinate—This property sets or returns a CLLocationCoordinate2D


structure that determines the annotation’s latitude and longitude.
title—This property sets or returns a string with the annotation’s removeAnnotation(MKAnnotation)—This method removes an
title. annotation. The argument is a reference to the object that represents
subtitle—This property sets or returns a string with the annotation’s the annotation.
subtitle. removeAnnotations([MKAnnotation])—This method removes
multiple annotations. The argument is an array of objects that
To create annotations, we must define a class that conforms to this represent the annotations we want to remove.
protocol and implements at least the three properties mentioned above.
showAnnotations([MKAnnotation], animated: Bool)—This
method zooms on a region that includes the annotations specified by
Listing 20-3: Defining an annotation
 the first argument. The animated argument indicates if the process
import MapKit will be animated.
class MyAnnotation: NSObject, MKAnnotation { selectAnnotation(MKAnnotation, animated: Bool)—This
var coordinate: CLLocationCoordinate2D
var title: String?
method selects an annotation (it shows the annotation as if the user
var subtitle: String? had tapped on it). The first argument is an object that represents the
init(coordinate: CLLocationCoordinate2D) {
annotation we want to select, and the animated argument indicates if
self.coordinate = coordinate the process will be animated.
}
} deselectAnnotation(MKAnnotation?, animated: Bool)—This
method deselects an annotation. The first argument is an object that
Annotations are created from our custom class and then added to the represents the annotation we want to deselect, and the animated
map. The MKMapView class offers the following properties and methods to argument indicates if the process will be animated.
add, remove, and manage annotations.
The following code expands the viewDidLoad() method from the previous
annotations—This property returns an array of MKAnnotation objects example to create an annotation from our MyAnnotation class and adds it to
that represent the annotations already added to the map. the map.
addAnnotation(MKAnnotation)—This method adds an annotation
to the map. The argument is an object of a class that conforms to the Listing 20-4: Adding an annotation
MKAnnotation protocol. 
import UIKit
addAnnotations([MKAnnotation])—This method adds multiple import MapKit
annotations to the map. The argument is an array of objects of a class class ViewController: UIViewController {
that conforms to the MKAnnotation protocol. @IBOutlet weak var mapView: MKMapView!

override func viewDidLoad() { IMPORTANT: The MapKit framework includes the MKPointAnnotation
super.viewDidLoad()
class to create basic annotations. If your annotations only require
let location = CLLocation(latitude: 40.7637825011971, longitude: -73.9731328627541) the values of the coordinate, title, and subtitle properties, you can create
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000,
longitudinalMeters: 1000) them from this class instead of defining your own (see Listing 20-12,
mapView.setRegion(region, animated: false)
below).
let annotation = MyAnnotation(coordinate: location.coordinate)
annotation.title = "Apple Store" As we already mentioned, an annotation object determines where the
annotation.subtitle = "Think Different"
mapView.addAnnotation(annotation) annotation is located, but the annotation view is the element responsible
} for displaying the graphic that points to the coordinates of the annotation.
}
If we do not provide a view for the annotation, the system creates one by

default from a class called MKMarkerAnnotationView. The views created from
By default, the Map Kit View generates a view to show the annotation on this class include the balloon shown in Figure 20-5, always with the same
the map. The view contains an image that represents a pin and the title icon and in the same color. If we want to change the configuration of the
below. When the annotation is selected, the view shows a larger image and view, we must create our own objects. For this purpose, the
the subtitle, as illustrated next. MKMarkerAnnotationView class includes the following initializer and
properties.
Figure 20-5: Annotation with a view by default
MKMarkerAnnotationView(annotation: MKAnnotation?,
reuseIdentifier: String?)—This initializer creates an annotation
view of type marker for the annotation specified by the annotation
argument. The annotation argument is a reference to the annotation
object we want to associate with the view, and the reuseIdentifier
argument is a string the Map Kit View needs to be able to reuse the
view to display multiple annotations.

glyphText—This property sets or returns the text displayed in the
Do It Yourself: Add to your project a Swift file called balloon.
MyAnnotation.swift for the code in Listing 20-3. Update the markerTintColor—This property sets or returns a UIColor value that
ViewController class with the code in Listing 20-4. Run the application determines the color of the balloon.
and tap on the pin to expand it (Figure 20-5). glyphTintColor—This property sets or returns a UIColor value that
determines the color of the text in the balloon.
glyphImage—This property sets or returns a UIImage object with the dequeueReusableAnnotationView(withIdentifier: String)—
image displayed in the balloon. The image must be of a size of 20 x 20 This method returns an MKAnnotationView object with the view
points. identified by the withIdentifier argument. The argument is the same
selectedGlyphImage—This property sets or returns a UIImage object string declared when the view was created.
with the image displayed in the balloon when it is selected. The image
must be of a size of 40 x 40 points. The ViewController class in the following example conforms to the
MKMapViewDelegate protocol and implements the delegate method to create
titleVisibility—This property sets or returns a value that determines a custom view for our annotation.
whether the title will be visible or not. It is an enumeration of type
MKFeatureVisibility with the values adaptive, hidden, and visible. Listing 20-5: Configuring the annotation view
subtitleVisibility—This property sets or returns a value that 
import UIKit
determines whether the subtitle will be visible or not. It is an import MapKit
enumeration of type MKFeatureVisibility with the values adaptive, hidden,
class ViewController: UIViewController, MKMapViewDelegate {
and visible. @IBOutlet weak var mapView: MKMapView!

override func viewDidLoad() {


Annotations are added to the map and then the Map Kit View checks super.viewDidLoad()
which ones are inside the visible area and requests the views to display. let location = CLLocation(latitude: 40.7637825011971, longitude: -73.9731328627541)
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000,
When the Map Kit View needs a view, it calls a delegate method to get it.
longitudinalMeters: 1000)
The method is defined in the MKMapViewDelegate protocol. To provide a mapView.setRegion(region, animated: false)
custom view for each annotation, we must conform to this protocol and
let annotation = MyAnnotation(coordinate: location.coordinate)
implement the following method. annotation.title = "Apple Store"
annotation.subtitle = "Think Different"
mapView.addAnnotation(annotation)
mapView(MKMapView, viewFor: MKAnnotation)—This method
is called on the delegate when the Map Kit View needs a view to show mapView.delegate = self
}
an annotation. The viewFor argument is a reference to the object that func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) ->
represents the annotation the map is going to display. MKAnnotationView? {
if let temp = annotation as? MyAnnotation {
var aView = mapView.dequeueReusableAnnotationView(withIdentifier: "Pins") as?
Like the cells of a Table View, annotation views are reusable. We must MKMarkerAnnotationView
if aView == nil {
create the view inside the delegate method, assign an identifier to it, and aView = MKMarkerAnnotationView(annotation: temp, reuseIdentifier: "Pins")
then use the same view again for other annotations. The MKMapView class aView?.glyphText = "Place"
includes the following method to get a reusable view. aView?.markerTintColor = UIColor.blue
aView?.titleVisibility = .hidden
aView?.subtitleVisibility = .hidden

} else {
aView?.annotation = annotation
}
return aView
}
return nil
}
}


The code in Listing 20-5 sets the region we want to show, adds an
annotation to the map at the coordinates of the Apple Store, and If we want to replace the balloon altogether, instead of using an object
designates the ViewController class as the Map Kit View’s delegate. The of the MKMarkerAnnotationView class, we must define our own annotation
protocol method is implemented next to provide a custom view for this view with an object of a basic class called MKAnnotationView (this is the
superclass of the MKMarkerAnnotationView class). The class includes the
annotation.
following initializer and properties to create and configure the view.
When the Map Kit View is showing the selected area, it detects that there
is an annotation inside and calls the mapView(MKMapView, viewFor:
MKAnnotation) method to get its view. This method checks whether the
MKAnnotationView(annotation: MKAnnotation?,
annotation is one of our custom annotations, looks for a view with the reuseIdentifier: String?)—This initializer creates an annotation
"Pins" identifier, and if there is no view, it creates a new one. Notice that view for the annotation specified by the annotation argument. The
the dequeueReusableAnnotationView() method may return a view that was annotation argument is a reference to the annotation object we want
previously used for another annotation, so we must assign the new to associate with the view, and the reuseIdentifier argument is a
annotation to it before returning the value (aView?.annotation = annotation). string the Map Kit View needs to be able to reuse the view to display
The configuration defined for our annotation view includes a title for the multiple annotations.
balloon, a blue background, and it hides the annotation’s title and subtitle.
image—This property sets or returns the image for the view.
The result is shown below.
leftCalloutAccessoryView—This property sets or returns the view
Figure 20-6: Custom balloon displayed on the left side of the callout bubble.
rightCalloutAccessoryView—This property sets or returns the
view displayed on the right side of the callout bubble.
displayPriority—This property sets or returns a value that
determines the annotation’s priority. It is a structure of type
MKFeatureDisplayPriority that can be initialized with a value from 0 to
1000. The structure defines three type properties to return an object
with standard values: required (1000), defaultHigh (750), and defaultLow 
(250).
clusteringIdentifier—This property sets or returns a string with the Our image replaces the balloon, and now the information is shown in a
callout bubble when the annotation is selected, as shown below.
name of the group to which the annotation belongs. This identifier is
used to cluster annotations when they are too close to each other.
Figure 20-7: Custom annotation view
isEnabled—This property sets or returns a Boolean value that
determines if the view can be selected.

The process to create a custom view is the same as before, but instead of
using the MKMarkerAnnotationView class to create the view, we must use the
MKAnnotationView class.
Also, these custom views present the information in a bubble on top of the
icon when the annotation is selected, so we must assign the value true to
the canShowCallout property if we want the user to be able to see it. The
following method creates a view with a custom image and a bubble to 
display the title and subtitle (the iconmap.png file is available on our
website). Do It Yourself: Download the iconmap.png image from our website
and add it to the Assets Catalog. Update the ViewController class from
Listing 20-6: Defining a custom annotation the previous example with the code in Listing 20-6. Run the
 application and tap on the icon. You should see something like Figure
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) ->
MKAnnotationView? {
20-7.
if let temp = annotation as? MyAnnotation {
var aView = mapView.dequeueReusableAnnotationView(withIdentifier: "Pins") The annotation view is responsible for the subview that presents the
if aView == nil {
aView = MKAnnotationView(annotation: temp, reuseIdentifier: "Pins") annotation’s callout bubble. Besides the title and the subtitle provided by
aView?.image = UIImage(named: "iconmap") the annotation, this subview can also include two small views or controls
aView?.canShowCallout = true on the sides. Because these views usually contain information specific for
} else {
aView?.annotation = annotation each annotation, we must define the data required to create the views in
} the annotation object. This may be images, text, decoration views, etc. For
return aView example, we can add a property to the MyAnnotation class to include a
}
return nil thumbnail that helps the user identify the location.
}

Listing 20-7: Adding custom data to an annotation


mapView.delegate = self

}
import MapKit func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) ->
MKAnnotationView? {
class MyAnnotation: NSObject, MKAnnotation { if let temp = annotation as? MyAnnotation {
var coordinate: CLLocationCoordinate2D var aView = mapView.dequeueReusableAnnotationView(withIdentifier: "Pins")
var title: String? if aView == nil {
var subtitle: String? aView = MKAnnotationView(annotation: temp, reuseIdentifier: "Pins")
var picture: UIImage? aView?.image = UIImage(named: "iconmap")
aView?.canShowCallout = true
init(coordinate: CLLocationCoordinate2D) {
self.coordinate = coordinate let leftImage = UIImageView(image: temp.picture)
} aView?.leftCalloutAccessoryView = leftImage
} } else {
 aView?.annotation = annotation
}
return aView
Now, besides the title and subtitle, we can also define a thumbnail for each }
annotation and then display that image in the callout bubble. Depending return nil
}
on which side we want the thumbnail to appear, we must assign it to the }
leftCalloutAccessoryView or the rightCalloutAccessoryView properties, as shown 
next.
The delegate method in Listing 20-8 adds a small image on the left side of
Listing 20-8: Configuring the callout bubble the callout bubble. The image is called appstore.png and contains a picture
 of the Apple Store (the file is available on our website). The result is shown
import UIKit below.
import MapKit
Figure 20-8: Callout bubble with a thumbnail
class ViewController: UIViewController, MKMapViewDelegate {
@IBOutlet weak var mapView: MKMapView!

override func viewDidLoad() {


super.viewDidLoad()
let location = CLLocation(latitude: 40.7637825011971, longitude: -73.9731328627541)
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000,
longitudinalMeters: 1000)
mapView.setRegion(region, animated: false)

let annotation = MyAnnotation(coordinate: location.coordinate)


annotation.title = "Apple Store"
annotation.picture = UIImage(named: "appstore")
mapView.addAnnotation(annotation)
User Location

Displaying the user's location on a map is easy with a Map Kit View, all we
need to do is to assign some values to a few properties provided by the
MKMapView class and the Map Kit View takes care of detecting the user's
current location and show it on the map. The following are some of the
properties provided by the class for this purpose.

showsUserLocation—This property sets or returns a Boolean value


 that determines if we want the Map Kit View to detect the user's
location.
Do It Yourself: Update the MyAnnotation class with the code in isUserLocationVisible—This property returns a Boolean value that
Listing 20-7 and the ViewController class with the code in Listing 20-8. determines if the user's location is currently visible on the map.
Download the appstore.png image from our website and add it to userLocation—This property returns an object of the MKUserLocation
the Assets Catalog. Run the application and tap on the pin to open class with the user's location.
the callout bubble.
To get the Map Kit View to determine the user’s current location, we must
assign the value true to the showsUserLocation property, but we must also ask
the user for permission. There are two types of authorization. We can ask
permission to get updates only while the app is active (the app is being
used by the user at the time), or all the time (even when the app moves to
the background). The Core Location framework defines the
CLLocationManager class to manage locations and get authorization from the
user. The following are some of the properties and methods included in
this class for this purpose.

authorizationStatus—This property returns the current


authorization status. The value is an enumeration of type
CLAuthorizationStatus with the values notDetermined, restricted, denied,
authorizedAlways, and authorizedWhenInUse.

requestWhenInUseAuthorization()—This method asks for The first thing we do in this view controller is to ask the user for
authorization to get the location while the app is in use. permission. The instance of the CLLocationManager object must remain in
memory, so we store it in a property called manager. To ask for permission,
requestAlwaysAuthorization()—This method asks for
we call the requestWhenInUseAuthorization() method, and to get the location, we
authorization to get the location when the app is active or in the
set the Map Kit View's showsUserLocation property to true.
background.
The process is automatic, but we must define an option in the info.plist file
called "Privacy - Location When In Use Usage Description" with a string
If the app is authorized to access the user's location, the Map Kit View
that explains the user why we need to access his or her location.
shows a circle to indicate the location, but the configuration of the map
The view controller also includes an Action for the Show button to zoom in
doesn't change. If we want the map to show the area around the location,
on the area, so once the location is determined, the user can tap this
we must set the visible region with the setRegion() method, as shown next.
button to get a close-up view of his or her location.

Listing 20-9: Detecting and showing the user's location Figure 20-9: User’s location on the map

import UIKit
import MapKit

class ViewController: UIViewController {


@IBOutlet weak var mapView: MKMapView!
var manager: CLLocationManager!

override func viewDidLoad() {


super.viewDidLoad()
manager = CLLocationManager()
manager.requestWhenInUseAuthorization()

mapView.mapType = .standard
mapView.isRotateEnabled = false
mapView.showsUserLocation = true 
}
@IBAction func showLocation(_ sender: UIBarButtonItem) {
let location = mapView.userLocation Do It Yourself: Connect the Show button with an Action called
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000,
longitudinalMeters: 1000) showLocation().
Update the ViewController class with the code in Listing
mapView.setRegion(region, animated: true) 20-9. Add the "Privacy - Location When In Use Usage Description"
}
} option to the info.plist file. Run the application on a device. You
 should see a window asking for authorization. Authorize the app to
access your location and press the Show button to see it on the map.
includes two options: Allow Once and Allow While Using App. If the user
The Map Kit View constantly updates the user’s location. To help us keep allows the app only once, every time the app is opened, it will ask for
track of these changes over time and detect errors, the MKMapViewDelegate permission again, which could be frustrating. To improve the user
protocol defines the following methods. experience, Apple provides a framework called CoreLocationUI which
includes a class called CLLocationButton to create a button that automatically
mapView(MKMapView, didUpdate: MKUserLocation)—This authorizes the app to access the user's location once. The class defines the
method is called on the delegate every time a new location is following properties to configure the aspect of the button.
determined. The didUpdate argument is an object with the user’s
current location. icon—This property sets or returns a value that defines the graphic to
display on the button. It is an enumeration of type CLLocationButtonIcon
mapView(MKMapView, didFailToLocateUserWithError: Error)
with the values none, arrowFilled, and arrowOutline.
—This method is called on the delegate when the Map Kit View
cannot determine the user’s location. label—This property sets or returns a value that determines the text
to display on the button. It is an enumeration of type
When the Map Kit View detects a new location, it calls the CLLocationButtonLabel with values that represent predefined titles. The

mapView(MKMapView, didUpdate:) method to report the result. Inside this values available are none, currentLocation ("Current Location"),
method, we can move the center of the region to the new location or sendCurrentLocation ("Send Current Location"), sendMyCurrentLocation
perform any other task necessary. ("Send My Current Location"), shareCurrentLocation ("Share Current
Location"), and shareMyCurrentLocation ("Share My Current Location").
Listing 20-10: Keeping track of the user’s location

cornerRadius—This property sets or returns a CGFloat value that
func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) { determines the radius of the button's corners.
mapView.setCenter(userLocation.coordinate, animated: true)
} fontSize—This property sets or returns a CGFloat value that
 determines the size of the font.

Do It Yourself: Add the method in Listing 20-10 to the ViewController


The authorization button can only be created from code, and the best way
class. Remember to conform to the MKMapViewDelegate protocol and
to incorporate an element to the interface from code is to add it as the
assign the view controller as the Map Kit View delegate, as we did in content of a Stack View. The following interface includes a Stack View at
Listing 20-8. Run the application and take a walk. You should see the the bottom with a width of 200 points and a height of 50.
map moving to keep your location visible.
Figure 20-10: Interface to test the authorization button
In the previous example, we requested authorization with the
requestWhenInUseAuthorization() method. This presents an Alert View that

import UIKit
import MapKit
import CoreLocationUI

class ViewController: UIViewController, MKMapViewDelegate {


@IBOutlet weak var mapView: MKMapView!
@IBOutlet weak var stackView: UIStackView!

override func viewDidLoad() {


super.viewDidLoad()
let button = CLLocationButton()
button.cornerRadius = 10
button.label = .currentLocation
stackView.addArrangedSubview(button)

mapView.mapType = .standard
mapView.isRotateEnabled = false
mapView.showsUserLocation = true

mapView.delegate = self
}
IMPORTANT: The button must be visible and easy to read. This is func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
the reason why the size of the Stack View is important. If Xcode let location = mapView.userLocation
let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 1000,
considers that either the title is not readable or the icon is not longitudinalMeters: 1000)
visible, it will show a warning message. You should always make sure mapView.setRegion(region, animated: true)
}
that there is enough space to display the title and icon in full. }

If the user has not yet authorized the app to access his or her location, an
Figure 20-11: User location button
Alert View is displayed on the screen to warn the user of what the app is
about to do, but if authorization has already been granted, the location is
determined immediately without prior notice. For this reason, we cannot
use the location when the button is pressed, we need to wait until we are
sure the location is available. And the best way to do it is by implementing
the delegate method introduced before. In the following example, we
show how to create a user location button and implement the delegate
method to zoom in to the location when it becomes available.

Listing 20-11: Implementing the user location button




Search
Do It Yourself: Add a Stack View to the scene with a Width

constraint of 200 points and a Height constraint of 50. Connect this
view to the ViewController class with an Outlet called stackView. Update The MapKit framework incorporates a service to translate addresses into
the ViewController class with the code in Listing 20-11. Remember to locations and find places of interest. The service is called Local Search and
disconnect the Show button in the Storyboard if you are not using it can take a freeform query string and return an array with the results. The
anymore. Run the application. You should see the user location query is created from the Request class included in the MKLocalSearch class.
button on the screen, as shown in Figure 20-11. Press the button. The following are some of the properties available for configuration.
You should see your location on the map.
naturalLanguageQuery—This property sets or returns a string with
the term or address we want to search.
region—This property sets or returns an MKCoordinateRegion structure
that determines the region in which the search is performed.

To perform a search, the MKLocalSearch class includes the following


initializer and method.

MKLocalSearch(request: MKLocalSearchRequest)—This
initializer creates an MKLocalSearch object to perform a search request.
start()—This asynchronous method performs a search and returns an
MKLocalSearchResponse object with the results.

The search returns an MKLocalSearchResponse object that contains the


following properties.

mapItems—This property returns an array of MKMapItem objects that


represent the results produced by the search.
boundingRegion—This property returns an MKCoordinateRegion
structure that determines the region occupied by the results
produced by the search.

let location = mapView.userLocation


let region = MKCoordinateRegion(center: location.coordinate, latitudinalMeters: 2000,
The Local Search service was designed to find all the places that match the longitudinalMeters: 2000)
query. The framework defines the MKMapItem class to represent a place. mapView.setRegion(region, animated: true)
The following are some of the properties included by this class to return
Task(priority: .high) {
the data from the place. await setAnnotations()
}
}
name—This property sets or returns a string with the place’s name. func setAnnotations() async {
let request = MKLocalSearch.Request()
phoneNumber—This property sets or returns a string with the request.naturalLanguageQuery = "Pizza"
place’s phone number. request.region = mapView.region

url—This property sets or returns a URL value with the URL of the let search = MKLocalSearch(request: request)
do {
place’s website. let results = try await search.start()
placemark—This property sets or returns an MKPlacemark object with let items = results.mapItems

additional information about the place. await MainActor.run {


self.mapView.removeAnnotations(self.mapView.annotations)
for item in items {
There are different ways an app can perform a search and display the if let coordinates = item.placemark.location?.coordinate {
places returned. As an example, we have decided to connect the Show let annotation = MKPointAnnotation()
annotation.coordinate = coordinates
button to an Action that sets the region around the user’s location, finds annotation.title = item.name
places that sell pizza, and creates the annotations to show them on the annotation.subtitle = item.phoneNumber
self.mapView.addAnnotation(annotation)
map. }
}
Listing 20-12: Searching for pizza places }
} catch {
 print("Error: \(error)")
import UIKit }
import MapKit }
}
class ViewController: UIViewController { 
@IBOutlet weak var mapView: MKMapView!
var manager: CLLocationManager!
The code in Listing 20-12 defines the setAnnotations() method to search for
override func viewDidLoad() { places associated with the term "Pizza" and set the annotations for each
super.viewDidLoad()
manager = CLLocationManager() location found. When the start() method returns the results, we remove the
manager.requestWhenInUseAuthorization() current annotations, get the coordinates of the location for each place, and
mapView.showsUserLocation = true
} assign the place’s name and phone number to the annotation's title and
@IBAction func showLocation(_ sender: UIBarButtonItem) { subtitle.

Do It Yourself: Remove the Stack View from the interface. Connect


the Show button with an Action called showLocations(). Update the
ViewController class with the code in Listing 20-12. Run the application
and press the Show button. You should see your location and the
pizzerias in the area.

The setAnnotations() method of our example looks for places only in the
current region. If the user scrolls the map to find new places, the
information is not updated. To improve the application, we can take
advantage of two methods defined in the MKMapViewDelegate protocol that
are called when the region changes.

mapView(MKMapView, regionWillChangeAnimated: Bool)—


This method is called on the delegate when the visible region is about
to change.
mapView(MKMapView, regionDidChangeAnimated: Bool)—
This method is called on the delegate after the visible region changed.

By implementing the mapView(MKMapView, regionDidChangeAnimated:) method,


we can update the annotations every time the user scrolls the map.
(Remember to conform to the MKMapViewDelegate protocol and assign the
ViewController class as the Map Kit View's delegate, as we did in previous
examples.)

Listing 20-13: Updating the annotations



func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if mapView.userLocation.location != nil {
Task(priority: .high) {
await setAnnotations()
}
}
}

Directions The request is sent to Apple servers for processing. The framework defines
the MKDirections class to perform the request and process the results. The

class includes the following initializer and method.

Maps are not only used to find places but also to find routes to get from
MKDirections(request: Request)—This initializer creates an
one place to another. The MapKit framework includes a set of classes to
MKDirections object with the request specified by the request
calculate a route and draw it on the map. The first class we need to
implement is called Request, which is defined inside the MKDirections class. argument.
The Request class generates a request for a route between two locations. calculate()—This asynchronous method performs the request and
The following are the properties available in this class to configure the returns an MKDirectionsResponse object with the routes found.
request.
The routes are returned as objects of the MKRoute class. The class includes
source—This property sets or returns the route’s starting point. It is the following properties to get the route’s information.
of type MKMapItem.
destination—This property sets or returns the route’s destination. It polyline—This property sets or returns the route’s geometry that we
is of type MKMapItem. can use to draw the route on the map. It is an object of the MKPolyline
class.
requestsAlternateRoutes—This property sets or returns a Boolean
value that determines whether multiple routes will be returned when steps—This property sets or returns an array of MKRouteStep objects
available. that describe every step the user needs to take to reach the
destination.
transportType—This property sets or returns a value that
determines the type of transportation used to travel the route. It is an advisoryNotices—This property sets or returns an array of strings
MKDirectionsTransportType structure with the properties automobile, walking,
with additional information that the user may need to travel the
transit, and any.
route, such as traffic jams or interruptions.

departureDate—This property sets or returns a Date structure that distance—This property sets or returns a CLLocationDistance value (a
determines the date of departure to help the system estimate the typealias of Double) with the route distance in meters.
better route. expectedTravelTime—This property sets or returns a TimeInterval
arrivalDate—This property sets or returns a Date structure that value with the expected travel time in seconds.
determines the date of arrival to help the system estimate the better
After the server returns the MKRoute object describing the route, we must
route.
present it to the user. There are different types of information we can
extract from these objects, but the most interesting is the geometry
provided by the polyline property, which allows us to draw the route on the From this method, we must return a renderer configured according to how
map. we want the layer to be drawn. The framework defines the
The Map Kit View displays the graphics in layers. There is a layer for the MKPolylineRenderer class (a subclass of a subclass of the MKOverlayRenderer
map, another for the labels and roads, and we can also add custom layers class) to create a renderer for a Polyline overlay.
to display our own graphics, including the route. The value returned by the
polyline property contains all the information required to create a layer that
MKPolylineRenderer(polyline: MKPolyline)—This initializer
we can add to the Map Kit View to draw the route. The MKMapView class
creates a renderer for a Polyline overlay.
offers several methods to add and remove layers. The following are the
most frequently used.
The MKPolylineRenderer class inherits from its superclasses a set of properties
we can use to configure the renderer. The following are the most
addOverlay(MKOverlay, level: MKOverlayLevel)—This method
frequently used.
adds a layer to the Map Kit View. The first argument is an object that
defines the layer, and the level argument is an enumeration of type
fillColor—This property sets or returns a UIColor object with the color
MKOverlayLevel that defines the level in which the layer will be placed.
to fill the path.
The possible values are aboveRoads (places the layer above roads but
below labels) and aboveLabels (places the layer above roads and labels). strokeColor—This property sets or returns a UIColor object with the
color of the stroke.
removeOverlay(MKOverlay)—This method removes a layer from
the Map Kit View. lineWidth—This property sets or returns a CGFloat value that
determines the path's width.
Adding the layer to the Map Kit View is like adding an annotation. As with
annotations, the Map Kit View calls a delegate method to get the The route’s origin and destination are set with MKMapItem objects (the
information necessary to draw the layer. The following is the method same type of objects we receive when we perform a search). The class
defined in the MKMapViewDelegate protocol for this purpose. includes the following initializer to create an MKMapItem object from an
MKPlacemark object.
mapView(MKMapView, rendererFor: MKOverlay)—This
method is called on the delegate when the Map Kit View needs a MKMapItem(placemark: MKPlacemark)—This initializer creates
renderer to draw a layer. The method must return an object of the an MKMapItem object with the information provided by the argument.
MKOverlayRenderer class (or any of its subclasses) with the renderer we The placemark argument is an object with the information about the
want to use to render the graphics. The rendererFor argument is a place (location, name, etc.).
reference to the object that represents the layer we want to draw.

The MKPlacemark class offers its own initializer to create the MKPlacemark
object we need to initialize the MKMapItem object. The code in Listing 20-14 defines two properties (origin and destination) to
store the information for the route’s starting point and destination. When
MKPlacemark(coordinate: CLLocationCoordinate2D)—This the scene is loaded, these values are initialized with the coordinates of two
initializer creates an MKPlacemark object with the coordinates specified places in New York (the Apple Store and Grand Central Terminal) and the
by the coordinate argument. visible region is set around the Apple Store area. For this example, we
decided to get the route when the Show button is pressed. The following
The first step to create a route is to define the MKMapItem objects for the are the Action for the button and the asynchronous method we need to
starting point and destination, as we do in the following example. calculate the route.

Listing 20-14: Setting the route’s starting point and destination Listing 20-15: Calculating the route
 
import UIKit @IBAction func showLocation(_ sender: UIBarButtonItem) {
import MapKit let request = MKDirections.Request()
request.source = origin
class ViewController: UIViewController, MKMapViewDelegate { request.destination = destination
@IBOutlet weak var mapView: MKMapView! request.requestsAlternateRoutes = false
var origin: MKMapItem!
var destination: MKMapItem! Task(priority: .high) {
await calculateRoute(request: request)
override func viewDidLoad() { }
super.viewDidLoad() }
func calculateRoute(request: MKDirections.Request) async {
let coordOrigin = CLLocationCoordinate2D(latitude: 40.7637825011971, longitude: let directions = MKDirections(request: request)
-73.9731328627541) do {
let placeOrigin = MKPlacemark(coordinate: coordOrigin) let results = try await directions.calculate()
origin = MKMapItem(placemark: placeOrigin) await MainActor.run {
let routes = results.routes
let coordDestination = CLLocationCoordinate2D(latitude: 40.7523809365088, longitude: let route = routes.first!
-73.9778321046893) self.mapView.addOverlay(route.polyline, level: .aboveRoads)
let placeDestination = MKPlacemark(coordinate: coordDestination) }
destination = MKMapItem(placemark: placeDestination) } catch {
print("Error: \(error)")
let region = MKCoordinateRegion(center: coordOrigin, latitudinalMeters: 2000, }
longitudinalMeters: 2000) }
mapView.setRegion(region, animated: false) 
mapView.delegate = self
}
} The showLocation() method creates a request, assigns the location values to

the origin and destination properties, and then runs an asynchronous task to
perform the request. The asynchronous method creates the MKDirections
object to process the request and then calls the calculate() method to get the
route. This method returns the results in an MKDirectionsResponse object,
which contains the routes property that returns the MKRoute objects with the
routes. Because we set the requestsAlternateRoutes property to false, the array
returned by this property only contains one route object, so we get the
first element and add its Polyline overlay to the map.
The next step is to implement the MKMapViewDelegate protocol method to
provide the renderer for the Polyline overlays.

Listing 20-16: Drawing the route



func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) ->
MKOverlayRenderer {
let render = MKPolylineRenderer(overlay: overlay)
render.strokeColor = UIColor.red
return render
}

The protocol method in Listing 20-16 creates an MKPolylineRenderer object 


with a red stroke for the layer. This renderer draws a red line between
locations, as illustrated below. Do It Yourself: Update the ViewController class with the codes in
Listings 20-14, 20-15, and 20-16. Run the application and press the
Figure 20-12: Route on the map Show button. You should see the route from the Apple Store to
Grand Central Terminal on the map.

Because we centered the visible area at the Apple Store, the user may not
be able to see the entire route unless the map is zoomed out or scrolled.
There are several things we can do to improve our application, but it
always depends on what we want to achieve and how the application is
organized. One alternative is to define the annotations for the starting
point and destination and then call the showAnnotations() method provided by

the Map Kit View to set a visible region that includes both annotations. should see the entire route on the screen, from the Apple Store to
This way, not only will the entire route be visible, but we also make sure Grand Central Terminal.
that the user can identify the places where the trip begins and ends.
There are two more methods defined in the MKMapViewDelegate protocol
Listing 20-17: Zooming out to show the route that could be useful when working with routes and destinations.

func calculateRoute(request: MKDirections.Request) async {
let directions = MKDirections(request: request) mapView(MKMapView, didSelect: MKAnnotationView)—This
do { method is called on the delegate when an annotation view is selected.
let results = try await directions.calculate()
await MainActor.run { mapView(MKMapView, didDeselect: MKAnnotationView)—
let routes = results.routes
let route = routes.first!
This method is called on the delegate when an annotation view is
self.mapView.addOverlay(route.polyline, level: .aboveRoads) deselected.
let annotation1 = MKPointAnnotation()
annotation1.coordinate = self.origin.placemark.coordinate Using these methods, we can draw the route to the destination selected by
annotation1.title = "Apple Store"
the user. The following example finds coffee shops nearby the Apple Store
self.mapView.addAnnotation(annotation1)
in New York City and implements the mapView(MKMapView, didSelect:) method
let annotation2 = MKPointAnnotation() to get the route between the Apple Store and the selected coffee shop.
annotation2.coordinate = self.destination.placemark.coordinate
annotation2.title = "Grand Central Terminal"
self.mapView.addAnnotation(annotation2) Listing 20-18: Calculating routes to the destinations selected by the user

self.mapView.showAnnotations([annotation1, annotation2], animated: true)
} import UIKit
} catch { import MapKit
print("Error: \(error)")
} class ViewController: UIViewController, MKMapViewDelegate {
} @IBOutlet weak var mapView: MKMapView!
var appleCoord: CLLocationCoordinate2D!

var route: MKRoute?

In the method of Listing 20-17, the annotations are added to the map override func viewDidLoad() {
super.viewDidLoad()
when results are received from the server. Because of this, the annotations appleCoord = CLLocationCoordinate2D(latitude: 40.7637825011971, longitude:
and the route are displayed at the same time and the map zooms in or out -73.9731328627541)
to show the entire route on the screen. let region = MKCoordinateRegion(center: appleCoord, latitudinalMeters: 2000,
longitudinalMeters: 2000)
mapView.setRegion(region, animated: false)
Do It Yourself: Update the calculateRoute() method with the code in mapView.delegate = self

Listing 20-17. Run the application and press the Show button. You Task(priority: .high) {
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "Coffee" func calculateRoute(request: MKDirections.Request) async {
request.region = region let directions = MKDirections(request: request)
do {
await searchCoffeePlaces(request: request) let results = try await directions.calculate()
} await MainActor.run {
} let routes = results.routes
func searchCoffeePlaces(request: MKLocalSearch.Request) async { if let oldRoute = self.route {
let search = MKLocalSearch(request: request) self.mapView.removeOverlay(oldRoute.polyline)
do { self.route = nil
let results = try await search.start() }
await MainActor.run { self.route = routes.first!
let items = results.mapItems self.mapView.addOverlay(self.route!.polyline, level: .aboveRoads)
for item in items { }
if let coordinates = item.placemark.location?.coordinate { } catch {
let annotation = MKPointAnnotation() print("Error: \(error)")
annotation.coordinate = coordinates }
annotation.title = item.name }
annotation.subtitle = item.phoneNumber func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) ->
self.mapView.addAnnotation(annotation) MKOverlayRenderer {
} let render = MKPolylineRenderer(overlay: overlay)
} render.strokeColor = UIColor.red
} return render
} catch { }
print("Error: \(error)") }
} 
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let destinationCoord = view.annotation?.coordinate {
The code in Listing 20-18 does not introduce anything new. We search for
let placeOrigin = MKPlacemark(coordinate: appleCoord) places related to the word "Coffee" around the Apple Store, create
let origin = MKMapItem(placemark: placeOrigin) annotations for every place found, and add them to the map. When the
origin.name = "Apple Store"
user selects a pin, the delegate method is executed, and we use the pin’s
let placeDestination = MKPlacemark(coordinate: destinationCoord) location to get the route between the Apple Store and the selected place.
let destination = MKMapItem(placemark: placeDestination)
destination.name = view.annotation?.title! Because the user can select a different place after a route was added to the
map, we store a reference to the route in the route property to be able to
let request = MKDirections.Request()
remove the overlay later.
request.source = origin
request.destination = destination
request.transportType = .walking Do It Yourself: Update the ViewController class with the code in
request.requestsAlternateRoutes = false
Listing 20-18. Remember to disconnect the Show button in the
Task(priority: .high) { Storyboard if you do not implement it anymore. Run the application
await calculateRoute(request: request)
} and tap on a pin. You should see the route from the Apple Store to
}
}
the selected place.

Route objects include additional information that may be relevant to the


CHAPTER 21 - DRAG AND DROP
user, including the steps it takes to travel the route and the total distance.
The next example is an Action we can connect to the Show button in our
interface to display an Alert View that includes the distance and the
estimated time it takes to get to the destination of the current route.

Listing 20-19: Showing additional information



@IBAction func showLocation(_ sender: UIBarButtonItem) {
if let current = route {
let distance = current.distance
let time = current.expectedTravelTime
var message = "Distance: \(distance) meters \r\n"
message += "Time: \(time) seconds"

let alert = UIAlertController(title: "Route", message: message, preferredStyle: .alert)


let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}

Do It Yourself: Connect the Show button with an Action called


showLocation().
Complete the method with the code in Listing 20-19.
Run the application, select a pin, wait for the route to appear, and
press the Show button. You should see an Alert View with the
distance and estimated time to complete the route in seconds.
21.1 Drag and Drop Drag
 

Drag and drop is a way to visually move data from one application to Drag refers to the action of dragging an element in our app to another app
another or from two windows of the same application. It is available on (or to another part of our app). For instance, we may have an application
devices that can share the screen with two or more windows, like iPads that shows pictures to the users, and we want to let them drag a picture to
and Mac computers. In Mac computers, the process is simple. We open the Photo Library to make it available to the rest of the applications.
two or more windows at the same time and use the mouse to drag an A drag operation is defined as an interaction between the user and the
element from one window to the other. On iPads, we must split the screen scene. The Drag and Drop framework defines the UIDragInteraction class to
in two. iPads include an icon with three dots at the top that we can tap to create it.
share the screen with other applications, as shown below.
UIDragInteraction(delegate: UIDragInteractionDelegate)—This
Figure 21-1: Tool to split the screen on iPads initializer creates an interaction to manage a drag operation. The
delegate argument is a reference to the object that is going to
respond to the interaction.

 The interaction must be assigned to the view we want the user to be able
to drag. The UIView class includes the following methods to add and remove
When we tap on the three dots, the system shows a small window with interactions.
three icons (Figure 21-1, right). The first icon assigns the whole screen to
the app, the second icon splits the screen in two to show the current app addInteraction(UIInteraction)—This method adds the interaction
on the left and an additional app on the right, and the third icon moves the
to the view.
app to an overlay that is displayed on top of other apps. If we tap on the
second or third icons, the screen will be shared by two apps, so we can removeInteraction(UIInteraction)—This method removes the
drag and drop elements between them. interaction from the view.

Drag and drop is performed on elements on the screen, but the data to be
transferred is determined from code. For this purpose, the Drag and Drop
framework defines the UIDragInteractionDelegate protocol. When the user
drags an element, the system calls methods define by this protocol to get
the data and respond to changes in the state of the operation. The
following are the most frequently used.

hasItemsConforming(toTypeIdentifiers: [String])—This method


dragInteraction(UIDragInteraction, itemsForBeginning: returns a Boolean value that indicates whether the session contains
UIDragSession)—This method is called on the delegate to get the elements of the UTI type indicated by the toTypeIdentifiers
objects to transfer to the destination app. The method must return an argument.
array of UIDragItem objects.
dragInteraction(UIDragInteraction, previewForLifting: When the user begins dragging an element on the screen, the interaction
UIDragItem, session: UIDragSession)—This method is called on object calls the dragInteraction(UIDragInteraction, itemsForBeginning:) method on
the delegate to get the data we want to transfer. The data can't be sent in
the delegate to get the configuration of the preview (the image the
its original format; it must be prepared for other apps to be able to read it.
user see on the screen while the element is being dragged). The
The Foundation framework defines the NSItemProvider class for this purpose.
method must return a UITargetedDragPreview object with the The class includes the following initializer to create an item provider from
configuration. an object.
dragInteraction(UIDragInteraction, sessionWillBegin:
UIDragSession)—This method is called on the delegate when the NSItemProvider(object: NSItemProviderWriting)—This
user starts moving the element across the screen. initializer creates an item provider to transfer data with drag and drop
dragInteraction(UIDragInteraction, session: UIDragSession, and copy and paste operations. The object argument is an object that
willEndWith: UIDropOperation)—This method is called on the conforms to the NSItemProviderWriting protocol and contains the data
delegate when the user finishes dragging the element. we want to transfer.

When a drag operation is performed, the system creates a session to Among others, the NSItemProvider class includes the following methods to
manage it. Every delegate method receives a reference to this session that retrieve the object.
we can use to access the data. The session is created from a class that
conforms to two protocols: UIDragDropSession and UIDragSession. These canLoadObject(ofClass: Type)—This method returns a Boolean
protocols define the following properties and methods to process the data. value that determines if the object in the item provider can be
converted to an object of the class specified by the ofClass argument.
localContext—This property returns the additional object associated loadObject(ofClass: Type, completionHandler: Closure)—This
to the item. method loads the object in the item provider, converts it to the data
canLoadObjects(ofClass: Type)—This method returns a Boolean type specified by the ofClass argument, and calls the closure specified
value that indicates whether the elements in the session can be by the completionHandler argument when the process is over. The
converted to the data type specified by the ofClass argument. closure receives an array of NSItemProviderReading with the results.
Item providers require the objects that contain the data to conform to the dragImage.image = UIImage(named: "husky")
dragImage.isUserInteractionEnabled = true
NSItemProviderWriting protocol, which defines properties and methods to dragImage.clipsToBounds = false
specify the data and its type. But this is usually not necessary. The most
let drag = UIDragInteraction(delegate: self)
common elements users drag are images and text, and classes like UIImage dragImage.addInteraction(drag)
and NSString already conform to this protocol. }
Once we have the item provider with the object we want to transfer, we func dragInteraction(_ interaction: UIDragInteraction, itemsForBeginning session:
UIDragSession) -> [UIDragItem] {
must prepare it to be used in a drag operation. The framework defines the if let image = UIImage(named: "husky") {
UIDragItem class for this purpose. let item = NSItemProvider(object: image)
let dragItem = UIDragItem(itemProvider: item)
return [dragItem]
UIDragItem(itemProvider: NSItemProvider)—This initializer }
return []
creates an item to send in a drag operation. The itemProvider }
argument is the object containing the data. }

itemProvider—This property returns the NSItemProvider object
associated with the item. This example assumes that we have an Image View in our scene connected
localObject—This property sets or returns an additional object to with an Outlet called dragImage. Using this Outlet, we assign the picture of a
husky to the view and the value true to the isUserInteractionEnabled property to
associate with the item. It is useful when we need to read the content
allow the user to interact with it. When the drag begins, the system shows
of the item from multiple methods.
an expanded image to indicate to the user that it is ready to initiate the
operation. If the image extends outside the view's boundaries, it may be
Applications that allow drag and drop must include an element on the
clipped. To avoid this, we assign the value true to the clipsToBounds property,
screen for the user to drag or an element for the user to drop other
so the image is always displayed in full.
elements. For our example, we are going to use an Image View at the top
Once the element to be dragged is ready, we configure the drag operation.
of the screen. The following view controller implements the minimum code
The first step is to create the UIDragInteraction object and add it to the Image
necessary to assign an image to the Image View and allow the user to drag
View. This object declares the view controller as the delegate, so we can
it to another application.
implement the dragInteraction(UIDragInteraction, itemsForBeginning:) method of
the UIDragInteractionDelegate protocol to provide the data. In this method, we
Listing 21-1: Allowing the user to drag an image
recreate the UIImage object with the picture of the husky, create the

NSItemProvider object with this image, and include it in a UIDragItem object.
import UIKit
Although this time we send only a single item, we can return multiple
class ViewController: UIViewController, UIDragInteractionDelegate { values and let the app on the other side decide which one to use.
@IBOutlet weak var dragImage: UIImageView!
The user can now drag the image of the husky to any other application.
override func viewDidLoad() { Figure 21-2, below, shows what we see when the app is opened in a split
super.viewDidLoad()

screen along with the Photo Library. If we drag the husky to the app on the the user is dragging. For this purpose, the UIDragInteractionDelegate protocol
right, the picture is added to the Photo Library. includes the dragInteraction(UIDragInteraction, previewForLifting:, session:) method.
The preview is defined by two objects: a UIDragPreviewTarget object to
Figure 21-2: Drag and drop operation determine for which view we are creating the preview, and a
UITargetedDragPreview object to define the preview with the new view. The
UIDragPreviewTarget class is a subclass of the UIPreviewTarget class, which
includes the following initializer.

UIDragPreviewTarget(container: UIView, center: CGPoint)—


This initializer creates an object that determines the view we are
creating the preview for. The container argument is a reference to the
view the user is dragging, and the center argument provides an
anchor point for the preview.

Once we have this reference, we must define the preview with the new
view. The framework defines the UITargetedDragPreview class for this purpose.
Do It Yourself: Create a new project. Download the husky image
This is a subclass of the UITargetedPreview class, which includes the following
from our website and add it to the Assets Catalog. Add an Image
initializer.
View at the top of the scene. Connect the Image View to the
ViewController class with an Outlet called dragImage. Complete the view
UITargetedDragPreview(view: UIView, parameters:
controller with the code in Listing 21-1. Run the application on an UIPreviewParameters, target: UIPreviewTarget)—This initializer
iPad. Tap the three dots at the top of the screen and select the split creates a preview from the view specified by the view argument. The
option in the middle (Figure 21-1). Open the Photo Library. You parameters argument is an object that determines the configuration
should see something like Figure 21-2. Drag the husky to the view on of the preview. For drag operations, it is created from a subclass called
the right. The husky image should be added to the Photo Library. UIDragPreviewParameters. The class includes the properties backgroundColor
and visiblePath to define the background color or clip the view. The
The system generates a preview of the element being dragged to provide
target argument is the UIDragPreviewTarget object that determines for
feedback to the user. Because we added the interaction to the Image View,
which view we are creating the preview.
the preview is created from this view and its content, but this is not what
the user is dragging. Only the picture is going to be added to the Photo
The UITargetedDragPreview object replaces the view dragged by the user with
Library, not the Image View. If we want the preview to reflect this, we must
a new view, so we must provide this view with the content we want to
customize the preview with the view we want to show instead of the view
display. For our example, we need to create an additional Image View to Drop
show the picture of the husky, as in the following example.

Listing 21-2: Defining the preview
In order for the user to be able to drop elements in our app, we must

func dragInteraction(_ interaction: UIDragInteraction, previewForLifting item: UIDragItem, session: provide an element on the interface capable of receiving this data. To add
UIDragSession) -> UITargetedDragPreview? { this capability to a view, the framework defines the UIDropInteraction class,
if let image = UIImage(named: "husky") {
which includes the following initializer.
let dragView = UIImageView(frame: CGRect(x: 0, y: 0, width: image.size.width, height:
image.size.height))
dragView.image = image
UIDropInteraction(delegate: UIDropInteractionDelegate)—
let centerPoint = CGPoint(x: dragImage.frame.midX, y: dragImage.frame.midY) This initializer creates an interaction to manage a drop operation. The
let target = UIDragPreviewTarget(container: dragImage, center: centerPoint)
let preview = UITargetedDragPreview(view: dragView, parameters:
delegate argument is a reference to the object that is going to
UIDragPreviewParameters(), target: target) respond to the interaction.
return preview
}
return nil Like the drag operation, the drop operation is managed from delegate
}
methods. The protocol is called UIDropInteractionDelegate. The following are

the most frequently used.
We not only have to provide the view but also determine the size it is going
to take. In this example, we decided to give the view the size of the picture. dropInteraction(UIDropInteraction, canHandle:
Next, we create a CGPoint structure with the values in the midX and midY UIDropSession)—This method is called on the delegate to know if
properties of the original view's frame and define the objects to create the the view can receive the elements dragged by the user. The method
preview. Notice that the UIDragPreviewTarget object is created with a must return a Boolean value to report the result.
reference of the Image View the user wants to drag and the
dropInteraction(UIDropInteraction, sessionDidUpdate:
UITargetedDragPreview object is created from the new Image View we just
UIDropSession)—This method is called on the delegate when the
defined, so this view effectively replaces the original and now the preview
user drags an element inside the view. The method must return a
only includes the picture of the husky.
UIDropProposal object to determine the type of operations allowed. The

Do It Yourself: Add the method in Listing 21-2 to the ViewController types of operations are defined by a UIDropOperation enumeration
class. Run the application, split the screen, open the Photo Library, value. The values available are cancel, forbidden, copy, and move.
and drag the picture. You should see the picture of the husky being dropInteraction(UIDropInteraction, performDrop:
dragged, with no white area around it. UIDropSession)—This method is called on the delegate when the
user performs the drop and the items are ready.

dropInteraction(UIDropInteraction, sessionDidEnter: class ViewController: UIViewController, UIDropInteractionDelegate {


@IBOutlet weak var dropImage: UIImageView!
UIDropSession)—This method is called on the delegate when the
user drags the element inside the area occupied by the view. override func viewDidLoad() {
super.viewDidLoad()
dropInteraction(UIDropInteraction, sessionDidExit: dropImage.isUserInteractionEnabled = true

UIDropSession)—This method is called on the delegate when the let drop = UIDropInteraction(delegate: self)
user drags the element outside the area occupied by the view. dropImage.addInteraction(drop)
}
dropInteraction(UIDropInteraction, sessionDidEnd: func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -
> Bool {
UIDropSession)—This method is called on the delegate when the let valid = session.canLoadObjects(ofClass: UIImage.self)
operation is over. return valid
}
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session:
The session for a drop operation is created from an object that conforms to UIDropSession) -> UIDropProposal {
return UIDropProposal(operation: .copy)
the UIDropSession protocol. The protocol defines the following method to get
}
the data. func dropInteraction(_ interaction: UIDropInteraction, performDrop session:
UIDropSession) {
session.loadObjects(ofClass: UIImage.self) { results in
loadObjects(ofClass: Type, completion: Closure)—This method if let list = results as? [UIImage], let image = list.first {
gets the objects in the session that can be converted to the data type self.dropImage.image = image
}
specified by the ofClass argument. The completion argument is a }
closure that is executed to process the result. The closure receives an }
}
array of NSItemProviderReading value that we must cast to the original 
data types.
As always, we first define the interaction and add it to the element on the
The drop session also conforms to the UIDragDropSession protocol, so we can interface that is going to respond to the operation. If the user drags an
implement the canLoadObjects(ofClass:) method to know if the session element over the view, the system calls the dropInteraction(UIDropInteraction,
sessionDidUpdate session: UIDropSession) method to know what kind of operation
contains a value we can process, and then call the loadObjects(ofClass: Type,
completion: Closure) method to load the data. For instance, the following
is admitted by the view. In this case, all we need is to copy the image (we
example allows the user to drag and drop an image from an external app to don't need the image to be removed at the origin), so we return a
UIDropProposal object with the value copy. If the user drops the element on
an Image View in the interface.
the view, the system first calls the dropInteraction(UIDropInteraction, canHandle:
UIDropSession) method to know if the view can handle the values being
Listing 21-3: Dropping images in an Image View

dropped. In this method, we check if the value can be casted as a UIImage
import UIKit object with the canLoadObjects(ofClass:) method and return the value
produced by the method (true if it was successful). If the value dropped by you enter the area of the Image View, the background color should
the user is valid, the system then calls the dropInteraction(UIDropInteraction, change to Teal (green). If you drag the element outside the area of
performDrop:) method to allow us to process the data. In this method, we get the view, the background should go back to normal.
the object with the loadObjects(ofClass:) method, cast it to a UIImage object,
and assign it to the Image View in the interface.

Do It Yourself: Create a new project. Add an Image View at the top


of the scene. Connect the Image View to the ViewController class with
an Outlet called dropImage. Complete the view controller with the
code in Listing 21-3. Run the application. Split the screen and open
the Photo Library (Figure 21-1 and 21-2). Drag an image from the
Photo Library to your application. The image should be assigned to
the Image View and show on the screen.

By implementing other delegate methods, we can have more control over


the operation. For instance, we can implement the following methods to
change the background color of the Image View every time the user drags
an image inside or outside the view.

Listing 21-4: Changing the background color



func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnter session: UIDropSession) {
dropImage.backgroundColor = .systemTeal
}
func dropInteraction(_ interaction: UIDropInteraction, sessionDidExit session: UIDropSession) {
dropImage.backgroundColor = .systemBackground
}
func dropInteraction(_ interaction: UIDropInteraction, sessionDidEnd session: UIDropSession) {
dropImage.backgroundColor = .systemBackground
}

Do It Yourself: Add the methods in Listing 21-4 to the ViewController


class. Run the application, split the screen, open the Photo Library,
and drag an image from the Photo Library to the application. When

Lists Listing 21-5: Defining the model to test drag and drop

 import UIKit

enum Sections {
Any view can be dragged or receive a drop, but the process requires a few case main
more steps for Table and Collection Views. These views are containers for }
struct ItemsData: Identifiable {
one or more views, so the system needs to know which views are going to
var id: UUID = UUID()
participate in the process and where to place the view that is being var image: UIImage!
dropped. For this reason, the UIKit framework defines specific classes and }
struct ApplicationData {
protocols to control drag and drop operations performed on these var dataSource: UICollectionViewDiffableDataSource<Sections, ItemsData.ID>!
elements. There are different versions for Table Views and Collection Views var items: [ItemsData] = []
but they all work the same way. For instance, the framework defines the init() {
UICollectionViewDragDelegate protocol to control a drag operation for Collection items.append(ItemsData(image: UIImage(named: "bagels")))
Views. The following are some of the methods defined by this protocol. items.append(ItemsData(image: UIImage(named: "brownies")))
items.append(ItemsData(image: UIImage(named: "butter")))
items.append(ItemsData(image: UIImage(named: "cheese")))
collectionView(UICollectionView, itemsForBeginning: items.append(ItemsData(image: UIImage(named: "coffee")))
items.append(ItemsData(image: UIImage(named: "cookies")))
UIDragSession, at: IndexPath)—This method is called on the items.append(ItemsData(image: UIImage(named: "donuts")))
items.append(ItemsData(image: UIImage(named: "granola")))
delegate to get the objects to transfer to the destination. The method items.append(ItemsData(image: UIImage(named: "juice")))
must return an array of UIDragItem objects. items.append(ItemsData(image: UIImage(named: "lemonade")))
items.append(ItemsData(image: UIImage(named: "lettuce")))
collectionView(UICollectionView, items.append(ItemsData(image: UIImage(named: "milk")))
items.append(ItemsData(image: UIImage(named: "oatmeal")))
dragPreviewParametersForItemAt: IndexPath)—This method is items.append(ItemsData(image: UIImage(named: "potato")))
called on the delegate to know which part of the cell should be shown items.append(ItemsData(image: UIImage(named: "tomato")))
items.append(ItemsData(image: UIImage(named: "yogurt")))
in the preview. The method receives the cell's index path and must
}
return a UIDragPreviewParameters object that determines the parameters }
var AppData = ApplicationData()
of the preview. The class includes the properties backgroundColor and

visiblePath to define the background color or clip the cell.
We also need a cell to show the images in the Collection View. This cell is
The process to drag a cell from a Collection View to another application is like the one we used before in the examples of Chapter 11, with the
like the one we used before for a single Image View, but we need a difference that now we must enable user interaction for the image and the
Collection View Controller and a model that provides the images to display. cell's content view to allow the user to perform a drag and drop operation.
The following is the model for this example.
Listing 21-6: Defining the cell to test drag and drop
 super.viewDidLoad()
import UIKit collectionView.dragDelegate = self
prepareDataSource()
class FoodCell: UICollectionViewCell { prepareSnapshot()
let picture = UIImageView() }
func prepareDataSource() {
override init(frame: CGRect) { let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
super.init(frame: frame) indexPath, itemID in
picture.translatesAutoresizingMaskIntoConstraints = false if let item = AppData.items.first(where: { $0.id == itemID }) {
picture.contentMode = .scaleAspectFit cell.picture.image = item.image
picture.isUserInteractionEnabled = true }
}
self.contentView.addSubview(picture) AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
self.contentView.isUserInteractionEnabled = true (collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
picture.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 8).isActive = true itemID)
picture.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: -8).isActive }
= true }
picture.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor, constant: 8).isActive func prepareSnapshot() {
= true var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
picture.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor, constant: -8).isActive snapshot.appendSections([.main])
= true snapshot.appendItems(AppData.items.map({ $0.id }))
} AppData.dataSource.apply(snapshot)
required init?(coder: NSCoder) { }
fatalError("Error") func collectionView(_ collectionView: UICollectionView, itemsForBeginning session:
} UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
} let row = indexPath.row
 if let image = AppData.items[row].image {
let item = NSItemProvider(object: image)
let dragItem = UIDragItem(itemProvider: item)
The purpose of this app is to allow the user to drag the thumbnail in a cell return [dragItem]
to another app (e.g., the Photo Library), so we must implement the two }
return []
delegate methods introduced before to provide the data to be transferred }
and define the area of the cell used to create the preview. To designate the func collectionView(_ collectionView: UICollectionView, dragPreviewParametersForItemAt
indexPath: IndexPath) -> UIDragPreviewParameters? {
view controller as the delegate, we assign self to the dragDelegate property let cell = collectionView.cellForItem(at: indexPath) as! FoodCell
let imageFrame = cell.picture.frame
provided by the UICollectionView class, as in the following example.
let preview = UIDragPreviewParameters()
preview.visiblePath = UIBezierPath(rect: imageFrame)
Listing 21-7: Defining a Collection View for drag and drop return preview
}
 }
import UIKit 

class DragViewController: UICollectionViewController, UICollectionViewDragDelegate {


override func viewDidLoad() {

The process is the same as before, the only difference is in how we instruct application, split the screen, open the Photo Library, and drag one of
the system to create the preview. What we need to do in our example is to the cells. You should see something like Figure 21-3.
determine the path around the area occupied by the image in the cell, so
only the thumbnail is used to create the preview. The UIDragPreviewParameters Of course, we can also drop elements in the Collection View. UIKit defines
class includes the visiblePath property to specify this path, which takes a the UICollectionViewDropDelegate protocol to control the process in a Collection
value of type UIBezierPath. This is a class defined in the UIKit framework to View. The following are some of the methods define by the protocol.
create paths based on coordinate values. One of the values taken by this
class is a CGRect structure, so we can use it to create a path from the frame collectionView(UICollectionView, canHandle: UIDropSession)
of the image, as we did in this example. Once the parameters are returned, —This method is called on the delegate to know if the Collection View
the user can drag the pictures in the Collection View to other applications. can receive the elements dragged by the user. The method must
return a Boolean value to report the result.
Figure 21-3: Dragging a cell from a Collection View collectionView(UICollectionView, dropSessionDidUpdate:
UIDropSession, withDestinationIndexPath: IndexPath?)—This
method is called on the delegate when the user drags an element
inside the Collection View. The method must return a
UICollectionViewDropProposal object to determine the type of operations
allowed. The types of operations are defined by a UIDropOperation
enumeration value. The values available are cancel, forbidden, copy, and
 move.

Do It Yourself: Create a new Project. Replace the scene with a collectionView(UICollectionView, performDropWith:
Collection View Controller. Click on the Collection View and set the
UICollectionViewDropCoordinator)—This method is called on the
delegate when the user performs the drop and the items are ready.
Estimate Size to None from the Size Inspector panel (see Chapter 11,
Figure 11-7). Download the thumbnails from our website and add The method receives an object that contains the items dragged by the
them to the Assets Catalog. Create a Swift file called user and the location in the Collection View where the items were
ApplicationData.swift for the model in Listing 21-5. Create a subclass dropped.
of UICollectionViewCell with the name FoodCell. Update this class with collectionView(UICollectionView, dropSessionDidEnter:
the code in Listing 21-6. Create a subclass of UICollectionViewController UIDropSession)—This method is called on the delegate when the
called DragViewController and assign it to the Collection View user drags the element inside the area occupied by the view.
Controller. Update this class with the code in Listing 21-7. Run the collectionView(UICollectionView, dropSessionDidExit:
UIDropSession)—This method is called on the delegate when the
user drags the element outside the area occupied by the view. Listing 21-8: Dropping elements in a Collection View

import UIKit
When an item is dropped, the system calls the collectionView(UICollectionView,
performDropWith:) method with a coordinator object that provides all the class DropViewController: UICollectionViewController, UICollectionViewDropDelegate {
information required to process the item. This coordinator is created from override func viewDidLoad() {
super.viewDidLoad()
an object that conforms to the UICollectionViewDropCoordinator protocol, which collectionView.dropDelegate = self
defines the following properties. prepareDataSource()
prepareSnapshot()
}
items—This property returns an array of objects that conform to the func prepareDataSource() {
let cellRegistration = UICollectionView.CellRegistration<FoodCell, ItemsData.ID> { cell,
UICollectionViewDropItemprotocol and contain the values of the item and indexPath, itemID in
its previous location (in case it was dragged from the same Collection if let item = AppData.items.first(where: { $0.id == itemID }) {
cell.picture.image = item.image
View). }
}
destinationIndexPath—This property returns an IndexPath structure AppData.dataSource = UICollectionViewDiffableDataSource<Sections, ItemsData.ID>
with the location of the cell where the items were dropped. (collectionView: collectionView) { (collection, indexPath, itemID) in
return collection.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item:
itemID)
The items are represented by an object that conforms to the }
}
UICollectionViewDropItem protocol. The protocol defines the following
func prepareSnapshot() {
properties to return information about the items. var snapshot = NSDiffableDataSourceSnapshot<Sections, ItemsData.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(AppData.items.map({ $0.id }))
dragItem—This property returns a UIDragItem object with the item AppData.dataSource.apply(snapshot)
dropped by the user. }
func collectionView(_ collectionView: UICollectionView, canHandle session: UIDropSession) -
previewSize—This property returns a CGSize structure with the size > Bool {
let valid = session.canLoadObjects(ofClass: UIImage.self)
of the preview. return valid
}
sourceIndexPath—This property returns an IndexPath structure with func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session:
the location of the cell that was dragged by the user. UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) ->
UICollectionViewDropProposal {
let proposal = UICollectionViewDropProposal(operation: .copy)
Again, the implementation is like what we would use for a normal drop return proposal
}
operation, but with the protocol methods defined specifically for Collection func collectionView(_ collectionView: UICollectionView, performDropWith coordinator:
Views. The following example allows the user to drop an image in the UICollectionViewDropCoordinator) {
Collection View and adds an item to the model with it. for item in coordinator.items {
let provider = item.dragItem.itemProvider
provider.loadObject(ofClass: UIImage.self) { result, error in

if error == nil {
if let image = result as? UIImage {
self.addImage(image: image)
}
} else {
print("Error: \(error!)")
}
}
}
}
@MainActor func addImage(image: UIImage) {
if let thumbnail = image.preparingThumbnail(of: CGSize(width: 80, height: 100)) {
let newItem = ItemsData(image: thumbnail)
AppData.items.append(newItem)
prepareSnapshot()
}
}
}

The UICollectionView class includes the dropDelegate property to declare the


drop delegate. In this example, we declare the view controller as the
delegate and then implement the required methods. The difference from
previous examples is in how the items are processed. We read the items in
the coordinator with a for in loop, extract the UIDragItem object from each
item, get the item provider, and finally call the loadObject() method as
before. Notice that the size of the image is reduced to be able to show it in
a cell in the Collection View. Once we get the thumbnail, we create a new
ItemsData structure with it, add it to the model, and update the snapshot to
show it on the screen.

Do It Yourself: Create a subclass of UICollectionViewController called


DropViewController and assign it to the Collection View Controller
added to the interface in the previous example (instead of the
DragViewController class assigned before). Run the application, split the
screen, open the Photo Library, and drag an image from the library
to the Collection View. You should see an item with that image
added at the bottom.
CHAPTER 22 - MAC CATALYST 22.1 Mac Catalyst

UIKit was developed to create applications for iPhones and iPads.


Applications for Mac computers were always developed with a framework
called AppKit. This is a completely different framework, optimized for
computers and laptops, that implements its own classes and values. This
means that apps developed for mobile devices don't work on Mac
computers and apps for Mac computers don't work on mobile devices.
Modern Macs equipped with ARM processors (e.g., M1), can run iPhone
and iPad apps, but the apps run unchanged. The problem is that input and
output devices on an iPhone or an iPad are different from those found on
the Macs. The differences are extreme. There is no mouse on an iPhone,
the computer screen is larger than the iPad screen, keyboards are different
or nonexistent, and more. To offer a better user experience and take
advantage of the extended capabilities of desktop and laptop computers,
apps must be adapted to work on Macs. But learning how to develop an
application all over again and do it for two different frameworks demand
time and can be error prone. This is the reason why Apple introduced Mac
Catalyst.
Mac Catalyst is a technology that allows developers to adapt iPad
applications to run on Mac Computers. This means that we can develop an
app that runs natively on Macs with UIKit and only a few changes.

Mac Apps the Scheme called My Mac that allows us to run the application on the
computer.

Figure 22-3: My Mac option
The first step to turn our iPad app into a Mac app is to select the option
from the app's settings. The option is in the General panel, inside the

Deployment info section, as shown below.

Figure 22-1: Mac option in the app settings


Do It Yourself: Create a new project. Click on the app's settings
option in the Navigator Area (Figure 5-12, number 6), open the
General panel, and click on the Mac option (Figure 22-1, number 1).
Next, select the option Optimize Interface for Mac and run the
application for the My Mac scheme (Figure 22-3). You should see an
empty window.

Xcode also adds a bundle identifier for the Mac version of our app, and
When the Mac option is selected (Figure 22-1, number 1), Xcode opens a
options to enable capabilities, such as access to the Camera or the Photo
window to ask for confirmation and then offers an option to select the kind
Library. Some of these options are automatically enabled, depending on
of adaptation we want. We can tell the system to scale the interface to look
the configuration set for the mobile version of our app, but other changes
the same as the iPad or optimize it for Mac. The Optimize Interface for Mac
require our assistance. For example, we need to provide the icons and any
option makes the interface look more like a native app, adding or removing
images that are specific for the Mac in the Assets Catalog. The option is
spaces, styling the fonts, adapting the buttons, and more.
available in the Attributes Inspector panel. For instance, if we select the
AppIcon set, and activate the Mac option from this panel (Figure 22-4,
Figure 22-2: Mac optimization
number 1), the set includes placeholders for the icons required by Macs.

Figure 22-4: Mac icons

In addition to this option, Xcode performs other changes to get the app to
work on the Mac. Something we will notice right away is a new option in 
The Storyboard can show the scenes with a Mac layout. The option is when we click on the + button next to the value we want to change (Figure
available along with the iPad configurations, as shown below. 22-7, number 1). In the example below, we assign a value of 50 points to
the constraint when the button is shown on a Mac (Figure 22-7, number 2).
Figure 22-5: Mac configuration for the Storyboard
Figure 22-7: Values for Mac

When we click on the Mac option (Figure 22-5, number 1), the popup
window is expanded to include two more options to configure the 
interface to match the iPad or to be optimized for Mac. For instance, the
figure below shows how a Filled button looks on an interface that matches
the iPad interface (left) and when it is optimized for Mac (right).

Figure 22-6: Mac interface mode

Of course, this optimization is not enough. An interface that looks good on


iPads may have elements in locations that are inappropriate for Mac apps,
or constraints may need adjustment for the elements to look better on the
massive screens of Mac computers. To adapt the interface, we can
implement the same tools provided for Size Classes (see Chapter 6). For
instance, we may modify the button's top constraint in our example with a
different constant for Macs. The option is called Idiom and is available

Conditional Code Mac" is shown on the console, otherwise the message "Prints in iOS" is
shown instead.

As we will see later, these conditionals are useful when some of the
implemented classes are not supported by one system or the other, but we
Although the system is responsible for converting UIKit code into AppKit
can also detect the system in code and implement an if else statement when
code, more often than not we must define specific code for one platform
the code in question is platform independent. As we have seen in Chapter
or another. For this purpose, the Swift compiler supports conditionals.
6, the UITraitCollection class includes the userInterfaceIdiom property for this
These are not normal if else statements, they are conditionals that are
purpose.
checked before the code is compiled and, therefore, we can use them to
View controllers include the traitCollection property to return the
select the code we want to compile according to the target device.
UITraitCollection object that defines the way the view is presented. From this
Conditionals are built using the keywords #if, #else, and #endif. The #if and
property, we can check the platform and execute code accordingly.
#else keywords work like the Swift conditionals if else, but because the code
is not within a block, the #endif keyword is required to indicate the end. Listing 22-2: Detecting the platform when the app is running
There are several parameters we can use to set the condition, but to detect 
whether the application is being compiled for the Mac, we can use the import UIKit
targetEnvironment() instruction and the value macCatalyst, as in the following
class ViewController: UIViewController {
example. override func viewDidLoad() {
super.viewDidLoad()
if traitCollection.userInterfaceIdiom == .mac {
Listing 22-1: Detecting the platform before compiling print("Prints in Mac")
 } else {
print("Prints in iOS")
import UIKit }
}
class ViewController: UIViewController { }
override func viewDidLoad() { 
super.viewDidLoad()
#if targetEnvironment(macCatalyst)
print("Prints in Mac")
#else
print("Prints in iOS")
#endif
}
}

This is a very simple example but illustrates how to work with these types
of conditionals. If we run this project on the Mac, the message "Prints in
Menu

Something that iPad apps don't have is a menu bar, which is a required
feature for Mac applications. To create a menu, we can add the Main Menu
option to the Storyboard.

Figure 22-8: Main Menu option in the Library
The bar is organized hierarchically. There is an element at the top
representing the main menu, elements that represent each menu of the
bar, such as File or Format, then elements inside that represent groups of

options with similar characteristics, such as New Scene or Open Recent,
and finally the elements that represent the options the user can select,
The Main Menu option adds a menu bar to the Storyboard with system
such as the New option in the menu of Figure 22-10 (number 1). To delete
predefined options that provide basic functionality to the app.
a menu or an option, we just select it and press the Delete button, but if
we want to insert a new option, we must consider whether the option is
Figure 22-9: Predefined menu
related to any of the available options, or it belongs to a separate group.
For instance, if we want to insert an option that is related to the New
option in the File menu, we can drag the Menu Command option from the
Library to the New Scene group.

Figure 22-11: Menu option
We can double-click on a menu to see the options, select a menu or an
option and press the Delete button to remove it, or edit the menus from
the Document Outline panel.

Figure 22-10: Menu options in the Document Outline panel
The New Scene group in the File menu will now contain two options, New
and Item, and the change will be reflected in the Storyboard.

Figure 22-12: New menu option

 Dragging this option inside a menu in the Storyboard adds a group with
two options. The group is called Inline Menu, and the options are called
Item 1 and Item 2.
In this example, we have removed a few options from the File menu and
added a new one. The option is added with the title Item and no key
Figure 22-15: New inline section
associated to it, but we can change these values from the Storyboard or
from the Attributes Inspector panel. The panel shows the values of every
single attribute, including the combination of keys that perform the action.
In the example below, we assign the title "Show Alert" to the option and
the keys Command + S, so the user can perform the action by selecting the
option from the menu or by pressing those keys.

Figure 22-13: Option title


There is also an option called Sub Menu to add more menus to the bar.

Figure 22-16: Submenu option



If our new option is not related to any of the options in the menu, we can
create a new group. For this purpose, the Library includes an option called
If this option is dropped on the menu bar, it adds a new menu (Figure 22-
Inline Section Menu.
17, left), but when we drop it between the options of a menu, it adds a
submenu with more options inside (Figure 22-17, right).
Figure 22-14: Inline section option
Figure 22-17: New submenu

After we have the option we want in the menu, we can connect it to an



Action in the view controller to perform a task. This procedure is like the
one we used to create Unwind Segues. We define a method with the
When the mouse is released, Xcode shows a list of all the actions in the
@IBAction modifier and then select it from the Storyboard. For example, the
responder chain, which includes our view controllers. The window shows
following view controller includes an action that opens an Alert View.
all the actions predefined by the system along with the showAlertView()
action we have just defined in the ViewController class.
Listing 22-3: Defining the action for a menu option

import UIKit
Figure 22-19: Selecting the action for the menu option

class ViewController: UIViewController {


@IBAction func showAlertView(_ sender: Any?) {
let alert = UIAlertController(title: "Show Alert", message: "Open from menu", preferredStyle:
.alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}


With the action ready, we go to the Storyboard and Control-Drag a line
from our option to the icon representing the First Responder (the view If we select the showAlertView() action (Figure 22-19, number 1), this method
controller that is going to perform the action). will be executed every time the user selects the Show Alert option in the
menu or presses the Command + S keys.
Figure 22-18: Connecting the option to the first responder
Do It Yourself: Drag the Main Menu option from the Library to the
Storyboard. Drag the Menu Command option from the Library to the

Document Outline panel and put it inside the New Scene group in state—This property sets or returns the option's state (indicated by a
the File menu (or create a new group with the Inline Section Menu checkmark on the left-hand side of the title). It is an enumeration of
option). You should have a new option highlighted in blue (Figure 22- type State with the values on, off, and mixed.
12). Select the option and change the title and the associated keys
from the Attributes Inspector panel (Figure 22-13). Update the Every time an option is about to be displayed on the screen the menu calls
ViewController class with the code in Listing 22-3. Control-drag a line the following method in the view controller that performs the action to
from the new option to the icon that represents the First Responder validate it.
and select the Action in your view controller (Figures 22-18 and 22-
19). Run the application on your Mac (My Mac scheme). Select the
validate(_ command: UICommand)—This method is called on the
responder (the view controller) to validate the command. It receives a
option from the menu or press the associated keys. You should see
reference to the UICommand object that represents the option.
an Alert View popping up over the window.

The validate()method is defined in the UIResponder class, which our view


Menu options sometimes change depending on the state of the app. For
controllers inherit from, so we can override it to provide our own
instance, the title of an option may change to communicate to the user
implementation. Because the method is called for every option selected by
that the action is now different, or the option may be disabled if it is not
the user, the first step is to identify the option we want to modify. For this
possible to perform the action anymore. To adapt the menu, we must
purpose, we can check the action assigned to the option with a selector, as
reconfigure the options. Menu options are created from the UICommand
shown next.
class, which includes the following properties for configuration.

Listing 22-4: Modifying a menu option


title—This property sets or returns a String value with the option's

title. import UIKit
discoverabilityTitle—This property sets or returns an optional String class ViewController: UIViewController {
value with a message that describes the purpose of the option. var alertDisplayed: Bool = false

action—This property sets or returns the selector that performs the @IBAction func showAlertView(_ sender: Any?) {
action. let alert = UIAlertController(title: "Show Alert", message: "Open from menu", preferredStyle:
.alert)
attributes—This property sets or returns the option's attribute. It is a let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
structure of type Attributes defined by the UIMenuElement class. The present(alert, animated: true, completion: {
structure includes three type properties to configure the option: self.alertDisplayed = true
})
destructive, disabled, and hidden. }
override func validate(_ command: UICommand) {
let action = command.action
switch action { value of type UIMenuBuilder to provide access to the menu and options
case #selector(showAlertView):
if alertDisplayed { for configuration.
command.attributes = .disabled
}
default: When the app is launched, this method is called on the AppDelegate class and
break this is our chance to customize the menu bar. The method receives a value
}
} of type UIMenuBuilder, which is a protocol with all the methods we need.
} The following are the most frequently used.

In this example, we include a Boolean property to check whether the Alert insertChild(UIMenu, atStartOfMenu: Identifier)—This method
View was already opened or not. When we open the File menu, the inserts an option or a group of options at the beginning of the menu
validate() method is called for every option. Here, we check that the option is indicated by the atStartOfMenu argument.
the one that performs the showAlertView() method and if the Alert View was insertChild(UIMenu, atEndOfMenu: Identifier)—This method
already shown, we deactivate the option assigning the value disabled to the inserts an option or a group of options at the end of the menu
attributes property. Next time we open the File menu, the option is grayed
indicated by the atEndOfMenu argument.
out.
insertSibling(UIMenu, beforeMenu: Identifier)—This method
Do It Yourself: Update the ViewController class with the code in inserts an option or a group of options before the option indicated by
Listing 22-4. Run the application, open the File menu, and click on the beforeMenu argument.
the Show Alert option. You should see the Alert View on the screen. insertSibling(UIMenu, afterMenu: Identifier)—This method
Close the Alert View and open the File menu again. The Show Alert inserts an option or a group of options after the option indicated by
option should be disabled this time. the afterMenu argument.
remove(menu: Identifier)—This method removes the option or
From the validate() method we can modify all the attributes of an option, but group of options indicated by the menu argument.
we can't remove or add options to the menu. This is a limitation we have
when the menu is added to the Storyboard. If we need to modify the
There are two types of menus, the main menu at the top of the screen,
options available while the app is running, we must create the menu from
and contextual menus that open when the user interacts with a view. To
code. The option is again provided by the UIResponder class, which defines
identify the system we are working with, the UIMenuBuilder protocol
the following method for this purpose.
includes the system property. This property returns a UIMenuSystem object
buildMenu(with: UIMenuBuilder)—This method is executed by with information about the menu. The class includes two type properties
the application before the menu bar is created. The method receives a to represent the menu system and two methods to process the menu.

main—This type property returns a reference to the main menu UICommand(title: String, image: UIImage?, action: Selector,
system (the menu bar). propertyList: Any?, alternates: [UICommandAlternate],
context—This type property returns a reference to the context menu discoverabilityTitle: String?, attributes: Attributes, state:
system. State)—This initializer creates a menu option. The title argument is
the option's title. The image argument is the image associated with
setNeedsRebuild()—This method tells the menu system to rebuild
the option. The action argument is a selector with the action we want
all the menus.
to perform when the option is selected. The propertyList argument is
setNeedsRevalidate()—This method tells the menu system that the the data we want to associate with the option. The alternates
menus need to be revalidated. It is used to update the menus when
argument is an array of alternative options. The discoverabilityTitle
the state of the app changes.
argument is a string that describes the purpose of the option. The
attributes argument is a structure of type Attributes with properties to
The system creates a standard menu, as the one we added to the
specify the style of the option (destructive, disabled, and hidden). And
Storyboard before, that we can edit from code. For this purpose, the
finally, the state argument is a State enumeration that specifies the
framework defines the UIMenu class. The class includes the following
option's initial state. The values available are on, off, and mixed.
initializer.
The framework includes a subclass of UICommand called UIKeyCommand to
UIMenu(title: String, image: UIImage?, identifier: Identifier?,
define options that can be selected with a combination of keys.
options: Options, children: [UIMenuElement])—This initializer
creates a UIMenu object to represent a menu or an option. The title
UIKeyCommand(title: String, image: UIImage?, action:
argument is the title of the element. The image argument is the
Selector, input: String, modifierFlags: UIKeyModifierFlags,
image associated with the element. The identifier argument is the
propertyList: Any?, alternates: [UICommandAlternate],
value we want to use to identify the element. The options argument
discoverabilityTitle: String?, attributes: Attributes, state:
is a structure that determines the type of element this object
State)—This initializer creates an option with a shortcut key. The title,
represents. An empty array indicates that the element is a menu (or a
image, action, propertyList, alternates, discoverabilityTitle,
submenu), and the displayInline property indicates that the element is a
attributes, and state arguments are the same defined for the
menu option. Finally, the children argument is an array with the
UICommand initializer. The input argument is the textual representation
elements we want to add.
of the key the user must press to perform the action, and the
modifierFlags argument is a property of a structure of type
The options are still defined by the UICommand class. The class includes the
UIKeyModifierFlags that determines the modifier key the user must press
following initializer to create them from code.
to perform the action. The properties available are alphaShift, shift, Listing 22-5: Adding a menu to the menu bar
control, alternate, command, and numericPad. 
import UIKit

The options are identified with a structure of type Identifier defined by the @main
UIMenu class. The structure provides the following initializer to create a class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
custom identifier. [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
UIMenu.Identifier(String)—This initializer creates an identifier for func application(_ application: UIApplication, configurationForConnecting
a menu option. The argument must be unique and therefore Apple connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
UISceneConfiguration {
recommends declaring it with a reverse domain, such as return UISceneConfiguration(name: "Default Configuration", sessionRole:
com.formasterminds-myapp.MyMenuBar.myOption. connectingSceneSession.role)
}
override func buildMenu(with builder: UIMenuBuilder) {
The Identifier structure also includes type properties used to identify super.buildMenu(with: builder)

standard options (those included in the menu by default). The properties if builder.system == .main {
available are application, file, edit, view, window, help, about, preferences, services, hide, let option = UICommand(title: "Show Alert", action:
#selector(ViewController.showAlertView(_:)))
quit, newScene, close, print, undoRedo, standardEdit, find, replace, share, textStyle, let mymenu = UIMenu(title: "Alerts", identifier:
spelling, spellingPanel, spellingOptions, substitutions, substitutionsPanel, UIMenu.Identifier("com.formasterminds.test.alerts"), options: [], children: [option])
builder.insertSibling(mymenu, afterMenu: .file)
substitutionOptions, transformations, speech, lookup, learn, format, font, textSize, }
}
textColor, textStylePasteboard, text, writingDirection, alignment, toolbar, fullscreen,
}
minimizeAndZoom, bringAllToFront, and root. 

To customize the menu bar, we must override the buildMenu(builder: To create a new menu, we specify the title of the UIMenu object, declare the
UIMenuBuilder) method in the AppDelegate class. This method is called when options argument with an empty array (or ignore it), and then assign the
the application is about to create the menu, so we can customize it before options we want to include in the menu to the children argument. In this
it is shown to the user. example, the menu's title is "Alerts", and an option is defined by a
The method may be called to configure the main menu or contextual UICommand object with the title "Show Alert". The menu can be added as a

menus, so we must first check that we have received a reference to the new menu to the menu bar or as a submenu. This is determined by the
menu we want to modify and then proceed to add or remove the elements method we use to add the menu and the identifier. In this case, we use the
we want. The following example adds a menu called Selection to the main insertSibling(UIMenu, afterMenu:) method to add the menu to the main menu

menu, next to the standard File menu. next to the File menu (the file identifier represents the File menu).

Notice that the action is defined with a selector (#selector) that calls the already defined in the Storyboard). Update the AppDelegate class with
showAlertView() method in our view controller. This is possible because the the code in Listing 22-5 and the ViewController class with the code in
selector looks for the method in the responder chain, which includes all Listing 22-6. Run the application. You should see the Alerts menu
the view controllers opened by the user. In this case, the selector asks the next to the File menu, and the option should perform the same
system to search for the showAlertView() method in the responder chain, the action as before.
method is found in the ViewController object, and executed, showing an Alert
View as before. IMPORTANT: In this example, we take advantage of the responder
For the method to be found by the selector in our view controller, we need chain to execute a method in the view controller, but we could have
to turn the @IBAction into an @objc method, as shown next. defined the action inside the AppDelegate class and then post
notifications to the view controllers or modify the model. There are
Listing 22-6: Defining the action in the view controller different programming patterns available. For more information, visit
 our website and follow the links for this chapter.
import UIKit
Of course, we can also add the option to an existent menu or remove some
class ViewController: UIViewController { of the standard menus and options included by the system, as we did
@objc func showAlertView(_ sender: Any) {
let alert = UIAlertController(title: "Show Alert", message: "Open from menu", preferredStyle: before from the Storyboard. Next, we show how to add the Show Alert
.alert) option to the File menu and how to remove elements.
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil) Listing 22-7: Modifying the main menu
} 
}
 import UIKit

@main
The main menu now includes an additional menu called Alerts with an class AppDelegate: UIResponder, UIApplicationDelegate {
option called Show Alert that opens an Alert View when pressed. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
Figure 22-20: New menu added from code }
func application(_ application: UIApplication, configurationForConnecting
connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole:
 connectingSceneSession.role)
}
override func buildMenu(with builder: UIMenuBuilder) {
Do It Yourself: Remove the main menu added before to the super.buildMenu(with: builder)

Storyboard (the buildMenu(with:) method is not called if a menu was if builder.system == .main {
let option = UIKeyCommand(title: "Show Alert", image: UIImage(systemName:
"trash"), action: #selector(ViewController.showAlertView(_:)), input: "S", modifierFlags:
[.command]) Listing 22-8: Defining a model to store the state of the menu
let mymenu = UIMenu(title: "", identifier: 
UIMenu.Identifier("com.formasterminds.test.showalert"), options: .displayInline, children: [option])
import Foundation
builder.insertSibling(mymenu, beforeMenu: .close)
struct ApplicationData {
builder.remove(menu: .edit)
var includeMenu: Bool = true
builder.remove(menu: .format)
}
}
var AppData = ApplicationData()
}

}

Now we can check the value of the includeMenu property in the
In this example, the Show Alert option is created with the UIKeyCommand buildMenu(with: UIMenuBuilder) method to include an option only when the
class, so we can assign a combination of keys to the option (Command + S). value is true.
The File menu created by the system includes an option identified by the
close value. Using this reference, we insert our option at the top of the Listing 22-9: Defining the menu according to the current state
menu and then remove the Edit and Format menus. The result is shown 

below. import UIKit

@main
Figure 22-21: New option in the File menu class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func application(_ application: UIApplication, configurationForConnecting
 connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) ->
UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole:
The option in the last example includes an image. If we need to modify this connectingSceneSession.role)
image, or any other attribute, we can implement the validate() method on }
override func buildMenu(with builder: UIMenuBuilder) {
the view controller, as we did before (see Listing 22-4), but if we want to super.buildMenu(with: builder)
modify the menu itself, we must ask the system to rebuild it with the
if builder.system == .main {
setNeedsRebuild() method. This means storing the state of the menu in a if AppData.includeMenu {
let option = UICommand(title: "Show Alert", action:
model and rebuilding it every time the state changes. As an example, we #selector(ViewController.showAlertView(_:)))
are going to implement a simple model with just one property to let mymenu = UIMenu(title: "", identifier:
UIMenu.Identifier("com.formasterminds.test.showalert"), options: .displayInline, children: [option])
determine if an option should be included or not. builder.insertChild(mymenu, atStartOfMenu: .file)
}

} in Listing 22-9 and the ViewController class with the code in Listing 22-
}
} 10. Add a button to the interface and connect it to the activateOption()
 method in the view controller. Run the application. You should see
the Show Alert option in the File menu. Press the button on the
In the view controller, we need to add an action to toggle the value of the
interface and open the File menu again. The option should be gone.
includeMenu property when a button on the interface is pressed.

Listing 22-10: Rebuilding the menu



import UIKit

class ViewController: UIViewController {


@IBAction func activateOption(_ sender: UIButton) {
AppData.includeMenu.toggle()

let main = UIMenuSystem.main


main.setNeedsRebuild()
}
@objc func showAlertView(_ sender: Any) {
let alert = UIAlertController(title: "Show Alert", message: "Open from menu", preferredStyle:
.alert)
let action = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}

This example assumes that we have added a button to the interface and it
is connected to an action called activateOption(). When the button is pressed,
this method toggles the value of the includeMenu property and calls the
setNeedsRebuild() method on the main menu. This forces the system to
recreate the menu and the option is removed or added according to the
property's current value.

Do It Yourself: Create a Swift file called ApplicationData.swift for


the model in Listing 22-8. Update the AppDelegate class with the code
Toolbar image—This property sets or returns the button's image.
 label—This property sets or returns the button's label.
action—This property sets or returns a selector with the action to
UIKit apps include Navigation Bars to provide additional functionality, but perform when the button is pressed.
they don't look good in Mac applications. Instead, Mac computers use a
toolbar at the top of the window where we can add all the buttons we The items for the toolbar are provided by a delegate object. The
need. The toolbar is created by the NSToolbar class. framework defines the NSToolbarDelegate protocol for this purpose. The
following are some of the methods included in this protocol.
NSToolbar(identifier: String)—This initializer creates a toolbar. The
identifier argument is a string we can use to identify the toolbar when toolbarDefaultItemIdentifiers(NSToolbar)—This method is
there are several available. called on the delegate when the toolbar needs the identifiers of the
items it has to show. The method must return an array of Identifier
The NSToolbar class includes properties to customize the bar. The following values with the identifiers of the items we want to include in the bar.
are some of the properties available for Mac applications created with Mac
Catalyst.
toolbar(NSToolbar, itemForItemIdentifier: Identifier,
willBeInsertedIntoToolbar: Bool)—This method is called on the
displayMode—This property sets or returns a value that determines delegate when the toolbar needs the item with the identifier specified
whether the bar is going to show icons or labels. It is a DisplayMode by the itemForItemIdentifier argument. The method must return an
NSToolbarItem object with the corresponding item.
enumeration defined in the NSToolbar class. The values available are
iconOnly, labelOnly, and iconAndLabel. toolbarAllowedItemIdentifiers(NSToolbar)—This method is
allowsUserCustomization—This property sets or returns a Boolean called on the delegate when the toolbar needs to know which items
value that determines whether the user is allowed to edit the bar. If to exclude. The method must return an array of Identifier values with
the value is true, a contextual menu allows the user to show the icons, the identifiers of the items we want to exclude.
the labels, or both.
Toolbars are not available on iOS. If we try to implement any of these
classes in an iOS application, we will get an error. For this reason, it is
The buttons on the bar are created from the NSToolbarItem class. The class
better to define the code separate from the view controller and use
includes the following initializer and properties to create the items.
conditionals. This is the approach we are taking with the delegate object in
the following example. We call it MyToolbar.
NSToolbarItem(itemIdentifier: Identifier)—This initializer creates
a new toolbar button. The itemIdentifier argument is a structure that
Listing 22-11: Defining the toolbar's delegate
contains a string to identify the item.

 different methods, we store them in two properties called firstItemID and


import UIKit secondItemID.
Something to consider when creating the delegate is that the
class MyToolbar: NSObject {
#if targetEnvironment(macCatalyst) NSToolbarDelegate protocol is not available in iOS, so we must declare the
let firstItemID = NSToolbarItem.Identifier("com.formasterminds.test.firstitem") methods separated from the class, which requires creating an extension
let secondItemID = NSToolbarItem.Identifier("com.formasterminds.test.seconditem")
#endif that conforms to the protocol, as we did in this example (see Extensions in
} Chapter 3).
#if targetEnvironment(macCatalyst)
extension MyToolbar: NSToolbarDelegate {
The implementation of the protocol methods is simple. The
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { toolbarDefaultItemIdentifiers() method must return an array with the identifiers
let identifiers: [NSToolbarItem.Identifier] = [firstItemID, secondItemID] of all the items we want to include in the bar (in our case, two), the
return identifiers
} toolbarAllowedItemIdentifiers() method must also return an array with the items
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, we want to allow the toolbar to include (in this case, two), and the
willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
toolbar(NSToolbar, itemForItemIdentifier: Identifier, willBeInsertedIntoToolbar: Bool)
var toolbarItem: NSToolbarItem?
method must provide the items' definitions.
if itemIdentifier == firstItemID { The items are created from NSToolbarItem objects. In our example, we create
let newItem = NSToolbarItem(itemIdentifier: itemIdentifier)
newItem.image = UIImage(systemName: "trash") two, one for each identifier. The first one includes an image of a trash can,
newItem.action = #selector(ViewController.openFirstItem) and the second item is represented by a plus sign. The selectors behave
toolbarItem = newItem
} else if itemIdentifier == secondItemID { the same way as those implemented for menu options. They search for the
let newItem = NSToolbarItem(itemIdentifier: itemIdentifier) methods in the responder chain and execute them.
newItem.image = UIImage(systemName: "plus.app")
newItem.action = #selector(ViewController.openSecondItem)
The view controller must create the NSToolbar object to represent the bar
toolbarItem = newItem and assign the bar to the Scene (the window). For this purpose, the
} UIWindowScene object includes the titlebar property, which returns a UITitlebar
return toolbarItem
} object, which in turn includes the toolbar property to assign our toolbar to
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { the Scene, as shown next.
let identifiers: [NSToolbarItem.Identifier] = [firstItemID, secondItemID]
return identifiers
} Listing 22-12: Creating a toolbar
}

#endif
 import UIKit

class ViewController: UIViewController {


The Identifier initializer requires a string to identify the item. This string var toolbarDelegate = MyToolbar()
could be anything, but to make sure it is unique, it is recommendable to
override func viewDidAppear(_ animated: Bool) {
declare it with an inverted domain. In this example, we need two, one for super.viewDidAppear(animated)
each item we want to include in the bar. Because they are required by #if targetEnvironment(macCatalyst)
if let scene = view.window?.windowScene, let bar = scene.titlebar { print messages on the console, but you can perform any task you
let toolbar = NSToolbar(identifier: "main")
toolbar.delegate = toolbarDelegate want.
toolbar.displayMode = .iconOnly
bar.toolbar = toolbar
} By default, the title on the bar is the app's name, but the UIScene class
#endif includes the following property to customize it.
}
@objc func openFirstItem() {
print("First Item") title—This property sets or returns the toolbar's title.
}
@objc func openSecondItem() { subtitle—This property sets or returns the toolbar's subtitle.
print("Second Item")
}
} The following are the changes we need to introduce to the viewDidAppear()
 method to define a custom title.

In this example, we create an instance of the MyToolbar class and assign the Listing 22-13: Assigning a custom title to the toolbar
object to the delegate property of the NSToolbar object, so the toolbar can 
call the methods in this delegate object when it needs the items to show override func viewDidAppear(_ animated: Bool) {
on the screen. After the delegate is defined, we configure the bar to only super.viewDidAppear(animated)
show icons (iconOnly), and then assign the bar to the Scene. The result is #if targetEnvironment(macCatalyst)
if let scene = view.window?.windowScene, let bar = scene.titlebar {
shown below. let toolbar = NSToolbar(identifier: "main")
toolbar.delegate = toolbarDelegate
toolbar.displayMode = .iconOnly
Figure 22-22: Mac toolbar bar.toolbar = toolbar
scene.title = "My App"
}
#endif
}
 

Do It Yourself: Create a new project. Configure the project to work When the toolbar is displayed on the screen, the system calls a method in
on Macs from the app's settings. Create a new file with a subclass of the view controller to make sure that the actions can be performed. The
NSObject called MyToolbar and complete the file with the code in following is the method defined in the UIResponder class for this purpose.
Listing 22-11. Update the ViewController class with the code in Listing
22-12. Run the application on the Mac. You should see a toolbar with canPerformAction(Selector, withSender: Any?)—This method is
two buttons, as illustrated in Figure 22-22. Press the buttons. A called in the view controller when the toolbar items are presented on
message should be printed on the console. In this example, we just

the screen. The first argument is a reference to the item's action, and disabled and the image replaced.
the withSender argument is a reference to the item itself.

The items are identified by the action they performed. For instance, we can
disable the action that executes the openFirstItem() method, as shown next.

Listing 22-14: Disabling an item



override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(openFirstItem) {
return false
}
return true
}

This method is similar to the validate() method we use to modify the options
of a menu. We can not only implement it to disable an item but also to
change it. For instance, the following method disables the first item and
replaces the image of the trash can with another one to reflect a new
state.

Listing 22-15: Modifying an item



override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(openFirstItem) {
if let item = sender as? NSToolbarItem {
item.image = UIImage(systemName: "trash.slash")
}
return false
}
return true
}

Do It Yourself: Add the method in Listing 22-15 to the ViewController


class. Run the application. You should see the item with the trash can
Gestures Do It Yourself: Create a new project. Update the ViewController class
with the code in Listing 22-16 and run the application on the Mac.

Move the mouse over the window. The view's background should
Most gestures available in mobile devices are also available in Mac change to yellow.
computer, but something users expect to be able to do on Macs that is not
available in mobile devices is to perform tasks when the pointer of the UIKit also includes the UIToolTipInteraction class to create a tooltip that shows
mouse is moving over an element. That is called the hover gesture. UIKit a message if the mouse remains over a view for a few seconds. This is not
includes the UIHoverGestureRecognizer class to detect this gesture. required in mobile applications, but it is a useful tool for Mac applications.
To create the tool, the class includes the following initializer.
UIHoverGestureRecognizer(target: Any?, action: Selector?)—
This initializer creates a gesture recognizer for the hover gesture. The UIToolTipInteraction(defaultToolTip: String)—This initializer
target argument is the object where the action will be executed, and creates a tooltip with the message specified by the defaultToolTip
the action argument is the method to be executed when the gesture argument.
is detected.
To add the tool to a view, the UIView class includes the following method.
The hover gesture is implemented as any other (see Chapter 8). We first
create an instance of the recognizer and then assign it to the view with the addInteraction(UIInteraction)—This method adds an interaction
addGestureRecognizer() method. to the view.

Listing 22-16: Adding a hover gesture recognizer The implementation is simple. We create the tooltip with the message we
 want to show and add it to the view. In the following example, we add the
import UIKit tooltip to the main view.

class ViewController: UIViewController {


Listing 22-17: Adding a tooltip to a view
override func viewDidLoad() {
super.viewDidLoad() 
let gesture = UIHoverGestureRecognizer(target: self, action: #selector(changeBackground)) import UIKit
view.addGestureRecognizer(gesture)
} class ViewController: UIViewController {
@objc func changeBackground() { override func viewDidLoad() {
view.backgroundColor = .yellow super.viewDidLoad()
} let tooltipInteraction = UIToolTipInteraction(defaultToolTip: "This is the view")
} view.addInteraction(tooltipInteraction)
 }
}

Views
Do It Yourself: Update the ViewController class with the code in

Listing 22-17. Run the application. Move the mouse over the window
and wait for a second. You should see a message popping up with There are a series of small changes we may introduce to a scene to
the text "This is the view". improve the interface for Mac computers. For instance, the
UISplitViewController class includes the following properties to set up the
IMPORTANT: The UIControl class includes the toolTip property to minimum and maximum width of the primary column.
define a tooltip. If your view inherits from the UIControl class (e.g.,
UIButton), you do not need to create a UIToolTipInteraction object. All minimumPrimaryColumnWidth—This property sets or returns
you have to do is to assign the message to the toolTip property and the minimum width of the primary column in points.
the tool is created for you. maximumPrimaryColumnWidth—This property sets or returns
the maximum width of the primary column in points.

The following property is also included in the UISplitViewController class to


apply a translucent effect to the primary column.

primaryBackgroundStyle—This property sets or returns the


background style for the primary column. It is a BackgroundStyle
enumeration value. To turn the view translucent in Mac computers,
we must assign the value sidebar.

The UILabel class also includes a property useful for Mac applications.

showsExpansionTextWhenTruncated—This property sets or


returns a Boolean value that determines if the system is going to
expand the text when the mouse is over a label which text has been
truncated (true if we want the system to show the full text).

Classes like UIButton and UISlider also include a property that may be helpful
when the application is running on a Mac.
preferredBehavioralStyle—This property sets or returns a value code makes sure that the switch is represented by a checkbox and assigns
that determines the style of the control. It is an enumeration of type the label "Option" to it.
UIBehavioralStyle with the values automatic (default), pad, and mac. If we
want the control to be shown with the style used for iPads, we must Figure 22-23: Switch on the Mac
assign the value pad to this property.

Another element that provides customization options for the Mac is the
Switch. By default, the switch created by the UISwitch class is represented 
by a checkbox on Mac computers, but we can define the style with the
following property.

preferredStyle—This property sets or returns a value that


determines the style of the switch. It is an enumeration of type Style
with the values automatic, checkbox, and sliding.

If we decide to go with a checkbox, we can also specify a label with the title
property.

Listing 22-18: Configuring a switch for the Mac



import UIKit

class ViewController: UIViewController {


@IBOutlet weak var myswitch: UISwitch!

override func viewDidLoad() {


super.viewDidLoad()
myswitch.preferredStyle = .checkbox
myswitch.title = "Option"
}
}

This example assumes that we have added a switch to the interface, and it
is connected to the ViewController class with an Outlet called myswitch. The

22.2 Multiple Windows In addition to the options provided by the system, we can open the app in
 a new window by creating a Scene from code. The UIApplication class offers
the following methods to manage Scenes.
iPads and Mac computers can open multiple instances of an application in
separate windows (Scenes), but this feature is not enabled by default. The requestSceneSessionActivation(UISceneSession?,
option to support multiple windows is available from the General panel in userActivity: NSUserActivity?, options:
the project's settings, as illustrate below. ActivationRequestOptions?, errorHandler: Closure?)—This
method activates an existing Scene or creates a new one. The first
Figure 22-24: Support Multiple Windows option argument is a reference to the Scene session we want to activate or
the value nil if we want to create a new one. The userActivity
argument is a reference to an object of type NSUserActivity that stores
the state we want to set when the Scene is opened. The options
argument is an object with information for the session associated
 with the Scene. And the errorHandler argument is the closure to be
executed if an error occurs.
Activating this option allows the application to work from multiple requestSceneSessionDestruction(UISceneSession, options:
windows. iPads and Mac computers provide built-in functionality for this UISceneDestructionRequestOptions?, errorHandler: Closure?)
purpose. For instance, if we compile the app to work on the Mac, an option —This method dismisses an existing Scene session. The first argument
appears on the File menu to create a new window. is a reference to the session we want to destroy, the options
argument provides information on how to remove the Scene, and the
Figure 22-25: Option to create new window
errorHandler argument is the closure to be execute if an error occurs.
requestSceneSessionRefresh(UISceneSession)—This method
requests the system to update the views in the Scene specified by the
argument.


If all we want is to open a new window, we can just call the
requestSceneSessionActivation() method with nil values, as in the following
iPads offer multiple alternatives to open the app in a new window. The
easiest way is to split the screen with the bottoms at the top and open the example.
app again (see Chapter 21, Figure 21-1). If the app allows multiple
windows, two instances of the same app will share the screen. Listing 22-19: Opening a new window

import UIKit NSUserActivity(activityType: String)—This initializer creates a
class ViewController: UIViewController {
new NSUserActivity object with the name specified by the argument.
@IBAction func openNewScene(_ sender: UIButton) { The activityType argument is a string to identify the state.
let app = UIApplication.shared
app.requestSceneSessionActivation(nil, userActivity: nil, options: nil)
} The following are some of the properties and methods provided by the
} class.

This example assumes that we have a button on the interface connected to title—This property sets or returns the activity's title.
the ViewController class with an action called openNewScene(). When the button userInfo—This property sets or returns a dictionary with the values
is pressed, we get a reference to the UIApplication object assigned to the app required to set or restore the state represented by the object.
and call the requestSceneSessionActivation() method on it to open a new window. becomeCurrent()—This method marks the activity as the one that
is currently in use.
Do It Yourself: Create a new project. Go to the app's settings, resignCurrent()—This method deactivates the activity.
configure the project to work on Macs, and check the Support
invalidate()—This method invalidates the activity.
multiple windows option to be able to open the app in multiple
windows (Figure 22-24). Add a button to the interface and connect it
How the state is restored depends on the characteristics of our application,
to the ViewController class with an action called openNewScene().
but the process to store the information that represents the state is always
Complete the class with the code in Listing 22-19 and run the
the same. We must create an NSUserActivity object, store the information we
application on your Mac. Click on the button to open the app in a
are going to need to set the session in the userInfo property, and then use
new window.
that information to configure the state of the session when a previous
session or a new one is created.
The window opened by the example in Listing 22-19 launches an instance To illustrate how this works, we are going to create a simple application
of the app with the configuration by default. This means that the app will with a Pop Up button to select a picture, an Image View to display the
display the initial screen as if it had been launched for the first time. But selected picture, and a button to open a new window.
this is not always appropriate. Users often expect the window to be in the
state it was before the app was closed. To set the initial state of the session Figure 22-26: Interface to test multiple windows on Mac
or recover an old state, the Foundation framework defines the NSUserActivity
class. From this class, we can create objects that contain the information
required to set the state of the session or restore a previous one.

import UIKit

class ViewController: UIViewController {


@IBOutlet weak var myButton: UIButton!
@IBOutlet weak var myPicture: UIImageView!

override func viewDidLoad() {


super.viewDidLoad()
configureButton()
initializePicture()
}
func configureButton() {
myButton.changesSelectionAsPrimaryAction = true
 myButton.showsMenuAsPrimaryAction = true

To store the list of pictures, we need a model with at least two properties, var listOptions: [UIAction] = []
for (index, item) in AppData.picturesList.enumerated() {
one to store the array with the names and another to store the index of listOptions.append(UIAction(title: item.capitalized, identifier:
the selected picture, as shown next. UIAction.Identifier(String(index)), handler: selectOption))
}
myButton.menu = UIMenu(children: listOptions)
Listing 22-20: Defining the model to test multiple windows }
 func initializePicture() {
let id = AppData.selectedPicture
import Foundation
let action = myButton.menu?.children[id] as? UIAction
struct ApplicationData { action?.state = .on
var picturesList: [String]
var selectedPicture: Int myPicture.image = UIImage(named: AppData.picturesList[id])
}
init() { func selectOption(action: UIAction) {
picturesList = ["bagels", "brownies", "butter", "cheese", "coffee", "cookies", "donuts", "granola", if let id = Int(action.identifier.rawValue) {
"juice", "lemonade", "lettuce", "milk", "oatmeal", "potato", "tomato", "yogurt"] AppData.selectedPicture = id
selectedPicture = 0 myPicture.image = UIImage(named: AppData.picturesList[id])
} }
} }
var AppData = ApplicationData() @IBAction func openNewWindow(_ sender: UIButton) {
 let activity = NSUserActivity(activityType: "com.formasterminds.images")
activity.userInfo?["selected"] = AppData.selectedPicture
In the view controller, we must feed the Pop Up button with all the values
let app = UIApplication.shared
in the model and initialize the interface with the first value on the list. app.requestSceneSessionActivation(nil, userActivity: activity, options: nil)
}
}
Listing 22-21: Storing the state in an NSUserActivity object 

To configure the interface, we have defined two methods: configureButton() }
}
and initializePicture(). In the configureButton() method, we configure the Pop Up }
button with the pictures' names. First, we assign the value true to the }

changesSelectionAsPrimaryAction and showsMenuAsPrimaryAction properties to make
sure that the button is rendered as a Pop Up button and the name of the There are different ways to read the current activities set by the
selected picture is displayed on the title. And then we populate the button application. In this example, we get them from the userActivities property of
with the list of pictures using a for in loop. On the other hand, the the ConnectionOptions object received by the method. This property returns
initializePicture() method gets the index of the selected picture from the an array of NSUserActivity objects, so we get the first one that matches the
model and updates the Pop Up button and the Image View with it, so the type used by the view controller and then read the value from its userInfo
selected picture is always shown on the screen. property with the "selected" key. The key returns an Int value that we
At the end of the view controller of Listing 22-21, there is an action for the assign to the selectedPicture property in the model, so the currently selected
Open Window button. When the button is pressed, this method creates an picture is shown on the new window.
NSUserActivity object, sets a value in the userInfo dictionary with the key
"selected" and the index of the selected picture, and executes the Do It Yourself: Remove all the elements on the interface and the
requestSceneSessionActivation() method on the UIApplication object with this code in your view controller. Add a Pop Up button, an Image View,
object to open the window. and a button with the title Open Window to the scene, as in Figure
To set the state of the Scene and get the new window to show the right 22-26. Connect the Pop Up button to the ViewController class with an
image, we must read back the value stored in the userInfo property when Outlet called myButton and the Image View with an Outlet called
the Scene is created by the SceneDelegate object, as shown next. myPicture. Connect the Open Windows button with an Action called
openNewWindow(). Complete the ViewController class with the code in
Listing 22-22: Reading the state from the NSUserActivity object Listing 22-21. Create a Swift file called ApplicationData.swift for the

model in Listing 22-20. Update the SceneDelegate class with the code in
import UIKit
Listing 22-22. Download the thumbnails from our website and add
class SceneDelegate: UIResponder, UIWindowSceneDelegate { them to the Assets Catalog. Run the application on the Mac or an
var window: UIWindow?
iPad, select a picture, and click on the Open Window button.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: Another window should open with the same picture selected before.
UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
IMPORTANT: In this example, we barely scratched the surface of
if let activity = connectionOptions.userActivities.first(where: { $0.activityType ==
"com.formasterminds.images" }) {
what is possible to do when working with NSUserActivity objects and
if let index = activity.userInfo?["selected"] as? Int { multiple windows. The topic is extensive and goes beyond the scope
AppData.selectedPicture = index

of this book. For more information, visit our website and follow the
links for this chapter.
CHAPTER 23 - APP STORE
23.1 Publishing Apple Developer Program
 

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 to which the users have access. Applications for Mac app requires a membership to the Apple Developer Program. The option to
computers can be sold separately, but mobile applications can only be sold enroll in this program is available on the developer.apple.com website. We
in the App Store. The process is performed from Xcode, but there are a must click on the Discover/Program options at the top of the screen, press
series of requirements we need to satisfy for our app to be published and the Enroll button, and follow the instructions to register an account for an
become available to the 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 for each 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’s servers.
We must upload the archive to App Store Connect for review.

Certificates, Provisioning Profiles, and Identifiers a list of options to select the type of values we want to work with, and the
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 23-1 shows the menu we see after we go to
developer.apple.com, click on Account, and select the option Certificates,
IDs & Profiles.

Figure 23-1: Web page to manage certificates, provisioning profiles, and


identifiers

In this page, we can create, edit, or remove certificates, provisioning


profiles and identifiers. The page contains two panels. The left panel offers
App Store Connect

The first step to submit our app is to create a record on Apple’s servers.
Apple has designated a website for this purpose, available at
appstoreconnect.apple.com. To login, we must use the same Apple ID and
password we use to access our account at developer.apple.com. Figure 23-
2 illustrates the options available after logging in.

Figure 23-2: App Store Connect menu
To add a new app, we must select the New App option and insert the app’s
information. The first window asks for the platform we have developed the
app for (in our case, iOS and macOS), the application’s name, the primary
language, the bundle ID, and a custom ID (SKU) that can help us identify
the app later. The name and language are values we already have, and the
SKU is a custom string, but the Bundle ID is a value generate by Xcode.
Xcode creates a Bundle ID and submits it to Apple servers when we enable
services from the capabilities panel. If our app does not use any of these
services, we can register a new Bundle ID from developer.apple.com (see
Figure 23-1), as explained in the form.

Figure 23-4: Bundle ID and SKU identifier
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.

Figure 23-3: Menu to add apps to our account 

After these values are inserted, we can press the Create button and Submitting the Application
complete the rest of the information. This includes the app’s description,

screenshots, and personal information. We also must select the option
Pricing and Availability on the left panel to set the price and where the
The application and its resources must be compiled for each platform in a
application will be available. Once all the information is provided, we can
single archive and then submitted to App Store Connect. We must create
finally press the Save button and go back to Xcode to upload the files.
an archive for iOS devices and another for macOS. The option is available
on the Xcode’s Product menu, but it is only enabled when the appropriate
device is selected on the Schemes. We can select a real device connected
to the computer, or we can use the Any iOS Device option for iOS apps or
the Any Mac option for macOS.

Figure 23-5: Archive option

After we click on this option, Xcode compiles the application and creates
the archive. The next window shows the archive and offers buttons to
validate and submit the app.

Figure 23-6: List of archives created for our apps


Figure 23-6 shows an archive created for an application called Test (number All the options are recommended. The first one tells Xcode to include code
1). The item representing the archive includes the date it was created, the that improves the app’s performance, and the second uploads the
app’s version, and the number of the build (we can send multiple builds to necessary information for Apple to be able to report errors and perform
App Store Connect and later decide which one we want to be reviewed by diagnostics.
Apple). The next window allows us to select how we want to sign the app. With
automatic signing, we let Xcode take care of everything for us
IMPORTANT: The app’s version and the number of the build (recommended).
(archive) are determined from the app’s settings editor (by default,
both values are set to 1.0). If we want to specify a different version, Figure 23-8: Option to select automatic signing
we must declare the numbers separated by one or two periods (e.g.,
1.0 or 1.2.5). The values represent different revisions of our app,
with the order of relevance from left to right. The actual meaning of
the value is arbitrary, but we are required to change it every time an
update is published to the App Store to reflect how big the update
was.

Although it is not required, we should always validate the archive before


submitting the app. The process allows Xcode to detect errors and suggest

how to fix them. To begin the validation process, we can press the Validate
App button (Figure 23-6, number 3). The first window presents options to
The last window displays a summary and provides a button to initiate the
tell Xcode how to configure the archive and what to include.
validation process. Once this process is over, if no errors are found, we can
finally submit our app to Apple servers by pressing the Distribute App
Figure 23-7: Options to configure the archive
button (Figure 23-6, number 2).
As we already mentioned, we may submit multiple archives to the server
(builds). For this reason, we must go back to the App Store Connect
website, open the description of our application, and select the archive we
just uploaded (it may take a few minutes to be available). Figure 23-9
shows the option with the archive (build) uploaded for an app in its version
3.3.

 Figure 23-9: Selecting the build to send to the App Store

Find more books at


www.formasterminds.com

J.D Gauchat
www.jdgauchat.com

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

You might also like