0% found this document useful (0 votes)
244 views

App Development Using iOS Icloud

Uploaded by

yolode
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
244 views

App Development Using iOS Icloud

Uploaded by

yolode
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 515

App Development

Using iOS iCloud


Incorporating CloudKit with
Swift in Xcode

Shantanu Baruah
Shaurya Baruah
App Development
Using iOS iCloud
Incorporating CloudKit
with Swift in Xcode

Shantanu Baruah
Shaurya Baruah
App Development Using iOS iCloud: Incorporating CloudKit with
Swift in Xcode
Shantanu Baruah Shaurya Baruah
Somerset, NJ, USA Somerset, NJ, USA

ISBN-13 (pbk): 978-1-4842-8757-6 ISBN-13 (electronic): 978-1-4842-8758-3


https://doi.org/10.1007/978-1-4842-8758-3

Copyright © 2023 by Shantanu Baruah and Shaurya Baruah


This work is subject to copyright. All rights are reserved by the Publisher, whether the whole or
part of the material is concerned, specifically the rights of translation, reprinting, reuse of
illustrations, recitation, broadcasting, reproduction on microfilms or in any other physical way,
and transmission or information storage and retrieval, electronic adaptation, computer software,
or by similar or dissimilar methodology now known or hereafter developed.
Trademarked names, logos, and images may appear in this book. Rather than use a trademark
symbol with every occurrence of a trademarked name, logo, or image we use the names, logos,
and images only in an editorial fashion and to the benefit of the trademark owner, with no
intention of infringement of the trademark.
The use in this publication of trade names, trademarks, service marks, and similar terms, even if
they are not identified as such, is not to be taken as an expression of opinion as to whether or not
they are subject to proprietary rights.
While the advice and information in this book are believed to be true and accurate at the date of
publication, neither the authors nor the editors nor the publisher can accept any legal
responsibility for any errors or omissions that may be made. The publisher makes no warranty,
express or implied, with respect to the material contained herein.
Managing Director, Apress Media LLC: Welmoed Spahr
Acquisitions Editor: Jessica Vakili
Development Editor: James Markham
Coordinating Editor: Jessica Vakili
Distributed to the book trade worldwide by Springer Science+Business Media New York, 1
New York Plaza, Suite 4600, New York, NY 10004-1562, USA. Phone 1-800-SPRINGER, fax (201)
348-4505, e-mail [email protected], or visit www.springeronline.com. Apress Media,
LLC is a California LLC and the sole member (owner) is Springer Science + Business Media
Finance Inc (SSBM Finance Inc). SSBM Finance Inc is a Delaware corporation.
For information on translations, please e-mail [email protected]; for reprint,
paperback, or audio rights, please e-mail [email protected].
Apress titles may be purchased in bulk for academic, corporate, or promotional use. eBook
versions and licenses are also available for most titles. For more information, reference our Print
and eBook Bulk Sales web page at http://www.apress.com/bulk-sales.
Any source code or other supplementary material referenced by the author in this book is
available to readers on the Github repository: https://github.com/Apress/
App-Development-Using-iOS-iCloud. For more detailed information, please visit
http://www.apress.com/source-code.
Printed on acid-free paper
For my parents and my son’s grandparents,
Meenu Baruah and Late Sailendar Nath Baruah
Table of Contents
About the Authors�������������������������������������������������������������������������������xv

About the Technical Reviewer����������������������������������������������������������xvii

Acknowledgments�����������������������������������������������������������������������������xix

Introduction���������������������������������������������������������������������������������������xxi

Part I: Basic App Development Using Swift Core Concepts���������1


Chapter 1: Xcode Introduction��������������������������������������������������������������3
About Xcode����������������������������������������������������������������������������������������������������������3
Installation and System Requirements�����������������������������������������������������������������5
Interface Introduction��������������������������������������������������������������������������������������������6
Chapter Summary�������������������������������������������������������������������������������������������������9

Chapter 2: CloudKit Overview�������������������������������������������������������������11


CloudKit at a Glance��������������������������������������������������������������������������������������������11
Setting Up CloudKit���������������������������������������������������������������������������������������������13
Exploring CloudKit Dashboard�����������������������������������������������������������������������������18
Accessing the iCloud Dashboard�������������������������������������������������������������������19
Understanding the Dashboard�����������������������������������������������������������������������19
Creating Book Tracker Table (Table, type, Indexes)����������������������������������������21
Summary������������������������������������������������������������������������������������������������������������25

v
Table of Contents

Chapter 3: Core Swift Concepts����������������������������������������������������������27


Variables�������������������������������������������������������������������������������������������������������������27
Let and Var�����������������������������������������������������������������������������������������������������27
Basic Types���������������������������������������������������������������������������������������������������������28
Classes, Structures, and Objects������������������������������������������������������������������������28
Array��������������������������������������������������������������������������������������������������������������30
Scope�������������������������������������������������������������������������������������������������������������32
Functions�������������������������������������������������������������������������������������������������������34
Beautifying Strings Using NSAttributedString�����������������������������������������������36
Life Cycle Methods����������������������������������������������������������������������������������������37
Type Casting��������������������������������������������������������������������������������������������������39
Loop Controls������������������������������������������������������������������������������������������������42
UI Color���������������������������������������������������������������������������������������������������������������45
Overview��������������������������������������������������������������������������������������������������������45
Guidelines for UI Color�����������������������������������������������������������������������������������46
System Colors�����������������������������������������������������������������������������������������������47
Dynamic System Colors���������������������������������������������������������������������������������48
Getting System Colors�����������������������������������������������������������������������������������49
Syntax������������������������������������������������������������������������������������������������������������49
UIImage and UIImageView�����������������������������������������������������������������������������53
UITextField�����������������������������������������������������������������������������������������������������56
UIAlertController��������������������������������������������������������������������������������������������60
UITableView���������������������������������������������������������������������������������������������������62
Summary������������������������������������������������������������������������������������������������������������69

Chapter 4: Book Tracker Basic App Building��������������������������������������71


Setting Up the Tab View Controller����������������������������������������������������������������������71
Creating a Tab Application�����������������������������������������������������������������������������������76
Summary������������������������������������������������������������������������������������������������������������85

vi
Table of Contents

Chapter 5: Adding Book Screen����������������������������������������������������������87


Designing the Add Screen�����������������������������������������������������������������������������������88
Assigning the Add View Controller File in Main.storyboard��������������������������������92
Running the Code������������������������������������������������������������������������������������������������93
Defining the UI Objects for the Add Screen���������������������������������������������������������97
Running the Program����������������������������������������������������������������������������������������114
Saving the Book Record in iCloud���������������������������������������������������������������������115
Data Validation Is an Important Step�����������������������������������������������������������115
Create a Database Function File������������������������������������������������������������������123
Preparation Before Saving the Book������������������������������������������������������������123
Function to Save Book Record��������������������������������������������������������������������124
Setting Value Before Calling saveBook��������������������������������������������������������128
Post Save�����������������������������������������������������������������������������������������������������131
Reset Field���������������������������������������������������������������������������������������������������134
Summary����������������������������������������������������������������������������������������������������������136

Chapter 6: Displaying the Book Records������������������������������������������137


Setting Up Display View Controller��������������������������������������������������������������������137
Assigning the Display View Controller File in Main.storyboard�������������������������141
Query the Book Table����������������������������������������������������������������������������������������142
Call the Query Book������������������������������������������������������������������������������������������147
Create a Table View�������������������������������������������������������������������������������������������149
Step 1: Define the Table Object��������������������������������������������������������������������151
Step 2: Extending the Table Delegate and Table Data Source���������������������152
Step 3: Setting the Table Delegate and Table Cell���������������������������������������152
Step 4: Drawing the Table����������������������������������������������������������������������������154
Step 5: Implementing numberOfRowsInSection������������������������������������������155
Step 6: Implementing the cellForRowAt������������������������������������������������������156
Step 6: Run the program�����������������������������������������������������������������������������157
vii
Table of Contents

Detailed Text Label��������������������������������������������������������������������������������������������158


Setting a Table Header��������������������������������������������������������������������������������������162
Summary����������������������������������������������������������������������������������������������������������164

Chapter 7: Deleting a Table Record��������������������������������������������������165


Trailing Swipe Function�������������������������������������������������������������������������������������165
Deleting book from CloudKit Database�������������������������������������������������������������169
Summary����������������������������������������������������������������������������������������������������������174

Chapter 8: Searching Data Screen����������������������������������������������������175


Create the Search View Controller��������������������������������������������������������������������175
Draw the Search Screen�����������������������������������������������������������������������������������175
Query for All Records to Enable Search������������������������������������������������������������181
Text Field Events, Operations, and Display��������������������������������������������������������186
Remove Constraints������������������������������������������������������������������������������������������191
Table Functions�������������������������������������������������������������������������������������������������192
Summary����������������������������������������������������������������������������������������������������������196

Chapter 9: App Development Part 2 Overview����������������������������������199


What Lies Ahead . . .�����������������������������������������������������������������������������������������199
iCloud Setup������������������������������������������������������������������������������������������������������200
Summary����������������������������������������������������������������������������������������������������������202

Part II: Overview����������������������������������������������������������������������203


Chapter 10: Redesigning the Display Screen������������������������������������205
Redesigning the UI of the Display Book Screen������������������������������������������������207
Initial Setup�������������������������������������������������������������������������������������������������207
Defining UI Objects for the Top Views����������������������������������������������������������207
Lifecycle Method and Initial Setup��������������������������������������������������������������214
Drawing the Screen�������������������������������������������������������������������������������������223

viii
Table of Contents

Table Setup��������������������������������������������������������������������������������������������������231
Top View Blocks�������������������������������������������������������������������������������������������243
Custom Delegation��������������������������������������������������������������������������������������������249
Define the Delegate Protocol�����������������������������������������������������������������������250
Implementing the Delegate�������������������������������������������������������������������������251
Calling the Delegate������������������������������������������������������������������������������������254
Summary����������������������������������������������������������������������������������������������������������256

Chapter 11: Adding a Book���������������������������������������������������������������257


Creation of View Controller and Linking It to the Tab Bar����������������������������������259
Inheriting Delegates������������������������������������������������������������������������������������260
Declaring Variables��������������������������������������������������������������������������������������260
Declaring Screen Objects����������������������������������������������������������������������������261
Screen Load Event and Initial Functions�����������������������������������������������������266
Displaying the Genre and Status Table��������������������������������������������������������278
Input Text Field Events���������������������������������������������������������������������������������285
Save the Book����������������������������������������������������������������������������������������������287
Saving Book Record to iCloud���������������������������������������������������������������������294
Reset Fields�������������������������������������������������������������������������������������������������300
Summary����������������������������������������������������������������������������������������������������������300

Chapter 12: Book Details View Controller����������������������������������������301


Initial Setup�������������������������������������������������������������������������������������������������������301
Create the View Controller���������������������������������������������������������������������������301
Class Inheritance�����������������������������������������������������������������������������������������302
Class Variables��������������������������������������������������������������������������������������������302
Initial Load Functions����������������������������������������������������������������������������������304
Setup�����������������������������������������������������������������������������������������������������������304
Set Book Details������������������������������������������������������������������������������������������307

ix
Table of Contents

Drawing the Screen������������������������������������������������������������������������������������������311


Screen Objects��������������������������������������������������������������������������������������������312
UI Object Code Snippets������������������������������������������������������������������������������313
UI Object Code Snippets������������������������������������������������������������������������������316
UI Object Code Snippets������������������������������������������������������������������������������325
UI Object Code Snippets������������������������������������������������������������������������������326
Drawing Screen�������������������������������������������������������������������������������������������328
Displaying the Book Details������������������������������������������������������������������������������333
Number of Sections�������������������������������������������������������������������������������������333
Number of Rows������������������������������������������������������������������������������������������333
Display the Table�����������������������������������������������������������������������������������������334
Row Height��������������������������������������������������������������������������������������������������336
Header View������������������������������������������������������������������������������������������������337
Header Height����������������������������������������������������������������������������������������������338
Defining the Custom Cell�����������������������������������������������������������������������������338
Tab Bar Function������������������������������������������������������������������������������������������342
Summary����������������������������������������������������������������������������������������������������������343

Chapter 13: Sharing Book with Other Users�������������������������������������345


Import CloudKit�������������������������������������������������������������������������������������������������347
Share Button Click Event����������������������������������������������������������������������������������348
Share Record Functions������������������������������������������������������������������������������������349
Cloud Sharing Call Back Function���������������������������������������������������������������������356
Summary����������������������������������������������������������������������������������������������������������357

Chapter 14: Edit Book�����������������������������������������������������������������������359


Calling the Edit View Controller�������������������������������������������������������������������������359
Edit View Controller�������������������������������������������������������������������������������������������360
Class Level Variable�������������������������������������������������������������������������������������361

x
Table of Contents

Navigation Bar���������������������������������������������������������������������������������������������361
Setup�����������������������������������������������������������������������������������������������������������363
Save Book����������������������������������������������������������������������������������������������������364
Update Book Database Functions����������������������������������������������������������������365
Summary����������������������������������������������������������������������������������������������������������367

Chapter 15: Book Delete�������������������������������������������������������������������369


Delete Block Button Action��������������������������������������������������������������������������������370
Custom Delete Book Function���������������������������������������������������������������������������371
Delete Book Database Function������������������������������������������������������������������������372
Summary����������������������������������������������������������������������������������������������������������374

Chapter 16: Book Notes��������������������������������������������������������������������375


Marking Book as FavoriteBook Notes Touch Up Inside Event���������������������������376
Book Notes View Controller�������������������������������������������������������������������������������377
Class Level Variables�����������������������������������������������������������������������������������378
Book Notes Variable������������������������������������������������������������������������������������������378
Class Level UI Objects���������������������������������������������������������������������������������������380
Initial Loading���������������������������������������������������������������������������������������������������383
Custom Table View Cell for Book Notes������������������������������������������������������������386
Draw the Notes Screen�������������������������������������������������������������������������������������388
Table View Function������������������������������������������������������������������������������������������389
Table View Trailing Swipe Control���������������������������������������������������������������������394
Delete Notes������������������������������������������������������������������������������������������������������399
Adding Notes�����������������������������������������������������������������������������������������������������400
Draw the Add Notes Popup��������������������������������������������������������������������������402
Add Notes Navigation Bar Function�������������������������������������������������������������404
Remove Constraints������������������������������������������������������������������������������������405

xi
Table of Contents

Save Book Method��������������������������������������������������������������������������������������������406


Database Save Book Method�����������������������������������������������������������������������408
Tab Bar Function�����������������������������������������������������������������������������������������������410
Summary����������������������������������������������������������������������������������������������������������411

Chapter 17: Book Reminder��������������������������������������������������������������413


Reminder Action Button������������������������������������������������������������������������������������415
Draw Reminder Screen�������������������������������������������������������������������������������������415
Save Reminder��������������������������������������������������������������������������������������������������418
Setup Reminder������������������������������������������������������������������������������������������������419
Reset Reminder������������������������������������������������������������������������������������������������424
Update Reminder����������������������������������������������������������������������������������������������425
Update Database with Reminder Date��������������������������������������������������������426
Remove Reminder Screen��������������������������������������������������������������������������������428
Summary����������������������������������������������������������������������������������������������������������429

Chapter 18: Mark Favorite����������������������������������������������������������������431


Frequency Button Action�����������������������������������������������������������������������������������432
Database Functions Update Favorite Status�����������������������������������������������������435
Animation Function�������������������������������������������������������������������������������������������438
Summary����������������������������������������������������������������������������������������������������������440

Chapter 19: Shared Books Tab����������������������������������������������������������441


Accept the Share Record: Scene Delegate�������������������������������������������������������441
Share Record Function��������������������������������������������������������������������������������������444
Shared Book Database Function�����������������������������������������������������������������������449
Shared Task Zones��������������������������������������������������������������������������������������������449
Query Functions������������������������������������������������������������������������������������������������451
Summary����������������������������������������������������������������������������������������������������������454

xii
Table of Contents

Chapter 20: Search Screen���������������������������������������������������������������455


Create the View Controller��������������������������������������������������������������������������������456
Class Variables��������������������������������������������������������������������������������������������������457
Class UI Objects������������������������������������������������������������������������������������������������458
Screen Setup����������������������������������������������������������������������������������������������������460
View Did Load����������������������������������������������������������������������������������������������460
Book Query function������������������������������������������������������������������������������������461
Database Function for Book Query��������������������������������������������������������������461
Setup�����������������������������������������������������������������������������������������������������������463
Draw Search Screen������������������������������������������������������������������������������������466
Text Field Function��������������������������������������������������������������������������������������������467
Editing Begin�����������������������������������������������������������������������������������������������467
Editing Changed������������������������������������������������������������������������������������������467
Editing End��������������������������������������������������������������������������������������������������470
Pressing the Return Key on Keyboard���������������������������������������������������������471
Drawing the Table���������������������������������������������������������������������������������������������472
Removing the Constraints���������������������������������������������������������������������������������472
Table Function���������������������������������������������������������������������������������������������������473
Summary����������������������������������������������������������������������������������������������������������476

Chapter 21: Packaging and Releasing����������������������������������������������477


Setting Up the Application Logo������������������������������������������������������������������������477
Build the Archive and Publish���������������������������������������������������������������������������478
Set Up Test Flight Account���������������������������������������������������������������������������479
Menu Option������������������������������������������������������������������������������������������������482
Distribute App����������������������������������������������������������������������������������������������482
Setting Up the App in App Store������������������������������������������������������������������������486

xiii
Table of Contents

Promote Development Database to Production Database��������������������������������487


Invite Test Users in Test Flight���������������������������������������������������������������������������490
Test-Driven Development����������������������������������������������������������������������������492
Summary����������������������������������������������������������������������������������������������������������493

Index�������������������������������������������������������������������������������������������������495

xiv
About the Authors
Shantanu Baruah is an Executive Vice
President leading new business acquisition
in the Life Sciences and Healthcare business
at HCL Technologies. With over 21+ years
of experience across multiple disciplines,
Shantanu has been a pioneer in the fields
of healthcare, life sciences, and digital and
information technology at HCL Technologies.
His leadership has guided delivery, practice
building, and development of market-leading
solutions to reach new heights. A leader
with global exposure, Shantanu has successfully led teams across India,
Singapore, France, and the United States. His technological expertise
and innovative leadership qualities have placed him at the forefront
of important business acquisitions. Shantanu is active in the App
development community and has an approved App on Apple App Store.
He has been recognized as one of the top 25 Healthcare IT Executives of
2020 by the IT Services Report. He is also recognized as one of the Top 50
Healthcare Leaders in Consulting by The Consulting Report Magazine in
August 2022. Shantanu lives in New Jersey. His philanthropic outreach
includes education for children in developing nations.

xv
About the Authors

Shaurya Baruah is a high school student of


the class of 2024, who attends The Peddie
School, an elite boarding school located in
New Jersey. He has a strong interest in Math,
Physics, & Computer Science. Some of his
relevant coursework include AP (college-level)
Computer Science A, Calculus BC, Physics C,
English Literature, and Statistics. Shaurya is
dedicated to giving back to his community,
as he founded a 501(c)(3) nonprofit, Academic Advancements, with the
mission to support the education of underprivileged students globally
through means of financial aid and tutoring. Shaurya also participates in a
variety of extracurricular activities, including Varsity Wrestling and Public
Forum Debate.

xvi
About the Technical Reviewer
Vishwesh Ravi Shrimali completed his
bachelor’s in mechanical engineering and
master’s in machine learning and artificial
intelligence. Currently, he is working at
Mercedes Benz Research and Development
as an ADAS Engineer. He has also coauthored
Machine Learning for OpenCV 4 (Second
Edition), Computer Vision Workshop, and
Data Science for Marketing Analytics (Second
Edition) by Packt. When he is not writing blogs
or working on projects, he likes to go on long
walks or play his acoustic guitar.

xvii
Acknowledgments
Writing a book on technology is quite a time-consuming task, for it is not
about writing your thoughts on paper but also writing code and validating
its completeness. I would like to thank my wife Kapila Sood and my
daughter Nitara Baruah for their undeterred support and patience as I put
together endless hours to bring this book to life. Their feedback on the
aesthetics of the App, which is an integral part of the book, and the outline
of the book content is invaluable.
I would also like to thank my mother Meenu Baruah who discovered
the writing itch in me quite early in my life and has always encouraged me
to pursue writing. While at times I remained uncertain, she never doubted
that one day I will be a published author. She is also the first published
author in our family, and I am certain I got my writing genes from her.
I would also like to thank my father, Late Sailender Nath Baruah, who
introduced me to the world of software back in the day when computers
were not ubiquitously available.
I cannot thank enough my Apress team for all their help in making
this book a reality. Aaron Black, who I pitched the book to, loved the idea
instantly and introduced me to the world of publishing. Susan McDermott,
for all the coaching and help in navigating through the publishing process;
Shonmirin, in helping with all coordination; Vishwesh Ravi Shrimali
and James Markham, for reviewing my code and making sure it works
as expected and for providing valuable comments in reorganizing the
chapters and content.
—Shantanu Baruah

xix
Acknowledgments

I am extremely thankful to my father, Shantanu Baruah, for introducing


me to the world of programming. He was always enthusiastic about
teaching me programming, ever since my early days. At first, I questioned
why he would push me to work hard when I was only an early middle
schooler, but now, as I look back, I realize how much his help supported
me in excelling in not only learning this subject but also having the great
fortune to author a book along with him.
I would also like to thank my mother, Kapila Sood, for all her help in
assisting me on this project.
I am also thankful to Ms. Joy Wolfe, who taught me computer science
in high school for the last two years. She has helped me expand my
knowledge to a variety of computer science languages, which has helped
me immensely in writing this book.
Finally, I want to thank the Apress team for their help and support in
making this book a reality.
—Shaurya Baruah

xx
Introduction
Over the last decade, the usage of iPhones has skyrocketed. Apple released
their first iPhone back in the summer of 2007. What started off as a small
idea, a handheld computer used to call and message people, has become
the new normal. Practically everyone carries a phone in their back-pocket.
The first smartphone, released in 1994, had just ten inbuilt Apps, including
Address Book, Calculator, Calendar, Mail, Notepad, and Sketch Pad,
among a few others. If you have an iPhone, you will probably realize that
you still have all these Apps. They have been around for over two decades!
The only difference today is that we have an App store, where we can
download about 2.2 million other Apps, if we so desire. This is a big jump
from the original ten.
Here is a scenario. You wake up one morning, pull out your iPhone,
and suddenly have an idea for a brilliant App, something humanity has
never seen before. However, you don’t know how to code, or where to even
start. If you have navigated your way to this book, you are probably in a
similar predicament. This book shouldn’t scare you. It is not a dictionary
filled with every single line of code that has ever existed. Rather, it is a
step-­by-­step guide, which will help you with everything you need to know
for creating a professionally appealing App, even if you are just a beginner
who has never written a line of code in your life.
The first part of the book will teach you the basics of coding as you will
build the initial version of your sample App (read the next section to learn
more about the App we will build). The second part of the book delves
deeper into exploring all additional features that the Apple ecosystem
offers, and we will create the professional version of the sample App.

xxi
Introduction

If that sounds intimidating, there is no reason to worry because this


book is designed to take you on a journey of learning; it is not a reference
book for all syntax and concepts. Some concepts may take multiple
sittings – even a few days. The answer sometimes could be right in front of
you the entire time. Demonstrate patience. Read at your own pace, build
along as you move from chapter to chapter, use the book as a reference,
and if code doesn’t compile or the concept becomes overwhelming, then
take a break and revisit the topic. You will be surprised how easy the
same concept will appear when you look at it the next time around with a
fresh mind.
This book breaks everything down in a simple way and will guide you
toward the right path in deciphering the complexities of building your
App. In the end, if you still need help, send us an email (shantanu.baruah@
gmail.com), and we will try to get back to you as soon as possible.
Thank you for reading. Enjoy your journey through the world of
programming.

About the Book Tracker App


The book is designed to teach you a new concept as you progress through
building the Book Tracker application. You will first learn the new idea,
and then implement it into your very own App. As you read through this
book, you will slowly make your very own iPhone application. Let us first
understand what App we will build.
Have you ever forgotten the book you just read last week? Do you want
a reliable way to make sure you’re reading consistently? Do you feel like
taking electronic notes, scanning portions of text for reference purposes?
How about creating a good reading list and sharing with your friends? The
Book Tracker App will allow you to perform all such functions and many
more. The App allows you to create a new book and give it additional
details (such as author name, genre, etc.). You can search for a book,

xxii
Introduction

take book notes, and share your reading list with friends. If you want to
change some of the details in your book, you can do that very easily with
the Update Feature. You can also delete a book whenever you like. In
summary, the book tracker App will have the following functions:

• Ability to add, update, and delete books

• Functionality to view book details

• Define book genre

• Classify book views by criteria such as a book currently


read, already read, or to be read

• Search books by book and author names

• Share books with other readers

• Set reminders

• Take exhaustive notes on any book in the library

• Mark a book as Favorite and view all favorite books in


one place

We will also learn about design concepts to create professional-looking


Apps. We will exhaustively leverage iCloud CloudKit APIs to persist data in
private secure databases. To create this App, we will use the language Swift
and the Xcode IDE.

xxiii
Introduction

A screenshot of the Book Tracker App is shown here:

Parts of the Book


The book is divided into two parts.

Part I – Basic App Building


In this part, users will get familiarized with Xcode and CloudKit (the cloud-­
based database). This part will help users to create a basic Book Tracker
App. The primary intent is to get you accustomed to the basics of building
an iPhone Application. The part also focuses on designing Application

xxiv
Introduction

UI using code (instead of Apple providing drag and drop interface) and
applying constraints for multiform display. For complex iPhone App
development, we recommend code-based UI creation and constraint
management, and though this is an advanced concept, we are introducing
this in Part I. At the end of Part I, we will have a basic functioning App.
This part also provides details on some core Swift language concepts.
This is a separate section for reference purposes only in case details
around a core concept need a revisit.

Part II – Advance App


This part teaches all the advanced concepts required to build any
professional looking App in the App Store. The following concepts will be
covered in this part:

• Enhancing the Book Tracker App – Robust


functionality, enhanced UI, and better usability

• Multiuser Mode – iCloud Database concepts for


sharing data across users with the right security setup

• Integration to iPhone native features – Siri,


Notification Center
• Introduction to Test Flight – How to make a beta release
before posting the App for review to the App Store, and
subsequently, how to submit the App to the App Store

Our Goal and Final Words


The goal of this book is to spread new ideas and to share our knowledge
with the world. Before writing this book, we worked together on creating
an iPhone App called Tracking Genie (it is available in the Apple Store).
While working on this project, we faced multiple issues with CloudKit, as

xxv
Introduction

there is lack of documentation available on this topic. This book will share
everything we learned along the way while making this App from scratch.
It will teach you everything you need to know in building iPhone App
CloudKit. If you are planning to create a native iOS application, this book
should help make that journey enriching and enjoyable.
The code is compatible with the latest iOS 15.0 version and is built
using Swift language in Xcode 13.3.1.

xxvi
PART I

Basic App
Development Using
Swift Core Concepts
CHAPTER 1

Xcode Introduction
In this chapter, you will learn about the platform we will use to write
our code. If you already have downloaded Xcode, and have a good
understanding of the system, feel free to skip this chapter and head to
Chapter 3 to learn about CloudKit.

About Xcode
For beginners, you will probably have no idea what Xcode is. No need to
worry, read through this short chapter, and you will be well versed with the
necessary Xcode concepts.
Apple has developed Xcode as its IDE platform for macOS users to
create native applications for all Apple devices. It was first released in
2003, and since then, it has received many updates. The latest edition is
version 13.3.1 (as of September 2021). It is a free download from the Mac
App Store; however, for a developer license, you need to pay around 100
dollars a year.
Although it may seem daunting at first, when you get used to
programming and the interface, Xcode is a brilliant platform for coding.
There are a few reasons why I fell in love with this IDE.
First, the auto-completion feature of Xcode is a great little tool
which will come in quite handy in writing your code. When you type any
character or phrase, a list of all the suggested functions pops up, so it’s
easy to find functions, their definition, and related elements, particularly

© Shantanu Baruah and Shaurya Baruah 2023 3


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_1
Chapter 1 Xcode Introduction

helpful if you are new to the syntax. Because of this feature, you don’t
have to spend a ton of time looking for help content. You can simply type
something like the name of the function and try finding it using the auto-­
complete feature.
I would also like to mention Playground, which is a great feature often
overlooked. Use Playground to test out different codes and for learning the
basics of programming. Playground also will give you instant results; the
program is always running, so you can see the results of your code in the
console. There’s no need to click any button or refresh the project. This
way, you don’t waste a lot of your time worrying about code structure at an
early stage of learning. It is a great way to train, learn new concepts, and get
better at programming.
Apps which span across devices (iPhone, iPad, and iOS) and use
multiple form factors often will demand their screen to be designed using
code. However, Xcode also has a Storyboard, where you can easily drag and
drop to create your own screen. This feature works fine for a simple-­looking
screen. We will start by making the screen with this feature, and later, we
will completely revamp the App by designing the screens using code.
Storyboard allows you to add any items to your screen, such as
labels, buttons, text fields, and so on. You can also change the color, text,
font sizes, and many more in the property’s screen. The reason most
programmers don’t use the storyboard in the final version of their App is
because when you create the screen using code, you can create complex
screen without worrying about overlapping objects and anchors, and it is
easier to draw the App the way you want it on different form factors of iOS
devices.
Apple released the language Swift in October 2014. Ever since then,
it has continued to grow, and it has become the most preferred language
for iOS coding. Prior to Swift, Apple had Objective-C as the primary
language. There are over 1.5 million jobs created around App design and
development since the launch of the App Store in 2008. The best part
about Swift is that it is not difficult for beginners to learn.

4
Chapter 1 Xcode Introduction

Before you get started with coding, consider reading the next section to
learn how you can install Xcode.

Installation and System Requirements


Apple has only created Xcode for its own Mac products. Xcode requires
running macOS 10.13.6 or later. You will need 11.2 GB of storage on your
Mac to install this application.
To install Xcode, search for it in the App Store. Look for the App that
looks like the image shown as follows.

Figure 1-1. App Store Xcode Download Screen

It will probably take some time to download, as it is a big file. After


the initial download is complete, it will ask you to agree to the terms and
conditions document. After you select agree, it will begin installing the
components.

5
Chapter 1 Xcode Introduction

Interface Introduction
When you first launch Xcode, your screen should look something like this.

Figure 1-2. Xcode Launch Screen

Your initial Xcode screen will have two columns. On the left side, you
will see three options that you can select right underneath the Xcode logo.
The first option will allow you to create an Xcode project. This is what we
will be using when we start creating our App. Underneath that, you will
see the button which allows you to clone an existing project. This is useful
when the project you are going to work on is quite like an existing project,
or when you need to do major changes to an already existing project.
If you select the third option, you will be able to search through files
on your computer. Most of the time, you will not need to use this feature,
because the right side of the screen will show the recent projects that you
have been working on. If you want to create a new playground, you will go
to File ➤ New ➤ Playground.
If you select Create a new Xcode Project, or go to File ➤ New ➤ Project,
it will ask you to select a template for your project. On the top of the screen,

6
Chapter 1 Xcode Introduction

you can choose what platform you would like to use (IOS, MacOS, etc.),
and you can choose which type of application you would like to make. For
the purposes of this book, we will select iOS, and the first option, App. If
you explore a little more, you will notice there are different types of Apps
too, such as games and messaging.
Selecting next will take you to the screen where you add additional
information, such as your product name and organization. Since we
are just going through the interface right now, feel free to fill out this
information however you want. Later, when we start building the Book
Tracker App, we can give more logical values and names. Make sure you
have selected “Storyboard” instead of Swift UI for the interface.

Note The Bundle Identifier must be unique. This name allows others


to find you in the App Store.

Figure 1-3. App Settings

7
Chapter 1 Xcode Introduction

This is the initial screen you will see when you first launch your
application. On the left-hand side, you will notice a list of different files
which we will be using throughout our App creation. The folder Test Run-­
Through is the name of the main App folder, which has many other files
within. The screen that is initially shown is where you will enter basic
settings. You can scroll through and add/change any details that you
desire. For now, we will stick to the default values.

Figure 1-4. App Storyboard

Here is the Main Storyboard screen. We will spend a lot of time here
during the beginning stages of our application.

8
Chapter 1 Xcode Introduction

Chapter Summary
In this chapter, we learned about Xcode, the integrated development
environment (IDE) platform we will use to develop our iPhone App. The
chapter provided a step-by-step guide to install Xcode and provided the
basic understanding of the Xcode interface. It is highly recommended to
explore the interface and learn about the different screen layouts before
you begin coding.

9
CHAPTER 2

CloudKit Overview
Ever since Apple announced CloudKit, a new avenue for cloud-based App
development has emerged for developers. CloudKit offers a great avenue
for Apps that need interactions with other App users. Prior to CloudKit,
developers used to create homegrown solutions from authentication to
data sync, which is not an easy task to do besides being vulnerable to
security breaches. CloudKit wraps all such complexities and provides a
scalable architecture to develop data-persistent, cloud-based applications.
In this chapter, we will learn some basics, including setting up
CloudKit the dashboard, and managing CloudKit-based applications.

CloudKit at a Glance
CloudKit was first introduced in 2014 and, since then, has gone through
many revisions. It is a simple way to integrate your applications (iOS,
WatchOS, MacOS, and TVOS) to iCloud. Apple also uses CloudKit to
natively integrate its own family of products. For example, when you Share
a Note with another user, Apple is using CloudKit as Backend as a Service
(BaaS) to make that share happen. Following are some key offerings that
we will learn in future chapters of the book:

• Share content among App users, you can natively


share data with other application users.

© Shantanu Baruah and Shaurya Baruah 2023 11


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_2
Chapter 2 CloudKit Overview

• Securely share and manage data, the security is


natively built in. You can define the access and role
other App users can have on your data sets.

• Data Privacy, the beauty of CloudKit is that the user's


data is stored in their own private databases (unless
you explicitly define it to be a public database or a
shared database). Even the creator of the application
cannot access the data of another user.

• Distributed Data set, your application user’s data are


stored in their own iCloud instance. You don’t have to
invest in data scaling as your user base grows.

• Multi-environment API support, Apple has made the


CloudKit API ubiquitously available across platforms.
You can have an Android version of your App accessing
data from CloudKit database using its open APIs.

• Up to 2GB of data without any charge, you can make


your application production ready with very little
investment

• Dev and Prod environment, you get a staging


environment, where you can play around. When you
make your application production ready, you can easily
upgrade it to Production.

• A Dashboard to define and manage databases.

12
Chapter 2 CloudKit Overview

Setting Up CloudKit
Before you can explore the CloudKit functions, you need to configure the
same in the Xcode interface. After iCloud is enabled, an entitlement file
is created which is required to access any iCloud features. The setup of
CloudKit is a five-step process.
Step 1: Open the Xcode project for which you want to set up CloudKit
and click on the Project icon in the project Navigator. The project
properties window will appear as shown in Figure 2-1.

Figure 2-1. Xcode Project Settings Signing Screen 1

Step 2: Before you add CloudKit to your project, you need to sign into
iCloud. This is required because CloudKit persists all data in iCloud. iOS
uses the logged in user to authenticate the user for all your application
data transactions. On the top bar in the middle screen, click on Signing &
Capabilities. From the screen presented, click on the Team field; if you
don’t find your name, then click on add an account and login using your
Apple ID. Once logged in, your screen will look like in Figure 2-2.

13
Chapter 2 CloudKit Overview

Figure 2-2. Xcode Project Settings Signing Screen 2

Step 3: Now that you have logged in, you can add the CloudKit
capabilities. In the Screen, click + Capability. A popup screen will be
presented, search for iCloud. Once found, double click on iCloud to add
the capability. You will be presented with a screen, as shown in Figure 2-3.

14
Chapter 2 CloudKit Overview

Figure 2-3. Xcode iCloud Extension Screen

Once added, the screen shown in Figure 2-4 will appear.

Figure 2-4. Xcode Project Settings Signing Screen 3

15
Chapter 2 CloudKit Overview

Step 4: If Status shows warning. Click on sign and enter your Apple ID
to get the provision profile and the signing certificate. Once signed in, the
screen will look like the one shown in Figure 2-5:

Figure 2-5. Xcode Project Settings Signing Screen 4

The middle section of the preceding screen is zoomed in Figure 2-6 for
better clarity.

16
Chapter 2 CloudKit Overview

Figure 2-6. Xcode Project Settings Signing Screen 5

Step 5: The iCloud settings has three parts: Services, Containers,


and CloudKit Dashboard. Based on the selection in the Services section,
the iCloud repository is notified on what kind of data it needs to store.
Container needs a unique name to hold your application data and content.
Finally, the Cloudkit Dashboard, when clicked, launches the iCloud portal
where you can visually manage all iCloud objects and perform required
CRUD operations. A brief description for each option is listed as follows:
• Services: Key-Value Storage: Enable this feature if you
want certain application configuration or preference to
be available in every instance of your App across iOS
devices. For example, application preference, A change
in value in one instance will be reflected in other
instances immediately. There are certain limitations to
how much space is available per user. Every user Key-­
Value space is limited to 1 MB per user. Also, 1024 is
the maximum number of keys you can have. For more
details, visit the official Apple developer site.

17
Chapter 2 CloudKit Overview

• Services: iCloud Documents: Use this option to store


file-based content like Word docs, complex drawings,
or any blob content.

• Services: CloudKit: Enable this feature to store


database records. This feature acts like a classic
relationship database. We will use this feature mostly in
our book.

• Containers: If you are using it for the first-time,


containers may appear empty. Container provides a
logical structure to hold your schema, data, indexes,
logs, and relationship. We need to have a minimum of
one container to use the CloudKit features. Please note,
the CloudKit container once created cannot be deleted.
We will create a container in the next section.

• CloudKit Dashboard: This is the single access point


to manage all your CloudKit functions. We will explore
this in the next section.

Exploring CloudKit Dashboard


CloudKit Dashboard is a convenient way to manage all CloudKit functions
in an easier, user-friendly way. Although everything that we can do using
the dashboard can be done using Swift programming, it is still the best way
to configure your application initial setup. We will configure the tables for
our Book Tracker App using the iCloud Dashboard. (Note: If the tables are
not defined, they get automatically created when the App saves a record to
the database.)

18
Chapter 2 CloudKit Overview

Accessing the iCloud Dashboard


For accessing the iCloud Dashboard, you can either go to the Xcode
application settings and click the CloudKit Dashboard button under
Signing & Capabilities settings screen (as shown in Figure 2-7) or

Figure 2-7. Xcode Project Settings Signing Screen 6

you can directly go to the URL https://icloud.developer.apple.


com/dashboard/ and login using your Apple ID and password.
Before we can do any setup in the CloudKit Dashboard, we need to create
a container from the Xcode interface. Please note, containers once created
cannot be deleted. To create a container, click the + icon under the Container.
For this book, we will use an already created Container called iCloud.Genie
(you have to use a different name as container names are unique).

Understanding the Dashboard


From the left sidebar, select the Container iCloud.Genie. You will be
presented with a screen like the one displayed in Figure 2-8.

19
Chapter 2 CloudKit Overview

Figure 2-8. iCloud Dashboard Home Screen

1. Container Permission: If you have more than one


developer, you can set the permission for each
developer using this option.

2. API Access: Using API Token, any web front


application can request an access to the Container
and its data and environment.
3. Data: Data is where all application data persists.
There are three types of databases, Private (only the
user will have access to its own data), Public (All
users of the applications can see data), and Shared
(Users can share data among a select set of users).
Data holds all application data, indexes, and its
permission as designed in an application schema
structure.

20
Chapter 2 CloudKit Overview

4. Schema: This is where we can define the Record


Types, set indexes and subscriptions, and
Security Roles.

5. Logs: Logs provide live and historical


application logs.

6. Telemetry: Provides a graphical representation


of important database analysis over period, such
as Requests, Server Latency, Error Count, Average
Request Size, and Push notifications.

7. Usage: Usage gives the application usage pattern


across Storage (Database and Asset), Users, Number
of Requests per second by Database Type. We can
see the usage by daily or monthly view.

 reating Book Tracker Table (Table,


C
type, Indexes)
In this section, we will create the schema structure for the Book Tracker
App that we are going to build. Please note, explicitly creating a table is
not necessary as, when we define the table in code, it automatically gets
created when you run it for the first time. Observe the following steps for
creating it through the interface.

1. From the dashboard page, click the Schema Icon.


Please note we are in a development environment.
Once we are done with our development, we will
promote it to the production environment. (Note:
We created iCloud.Genie from the Xcode interface.)
Figure 2-9 shows the selected schema detail
dashboard.

21
Chapter 2 CloudKit Overview

Figure 2-9. iCloud Dashboard Home Screen

• Content Types are Record Types storing the definition


of a table. A Table has both system- and user-defined
attributes that define the character of the table. From
the screen, add a Content Type and name it as Book
and click the save button. Figure 2-10 is the record
types definition and editing screen.

22
Chapter 2 CloudKit Overview

Figure 2-10. iCloud Dashboard Record Types Screen

2. We will add the fields listed in Table 2-1 to the Book


Record Type. From the screen, click the Add Field
and add the following fields to the table. Make sure
you click the save button before exiting the screen.
Please note, we will create a few more tables in Part
II of this book when we create the advanced version
of our Book Tracker App. Table 2-1 lists all the field
details of the schema type Book.

23
Chapter 2 CloudKit Overview

Table 2-1. Table Schema Details


Custom Field Name Type Description

BookID String An ID to uniquely identify books


bookname String Book Name
authorname String Author Name
genre String Book Genre
status String Current Status of the Book (Reading,
Read, Currently Reading)

3. To make the fields queryable/sortable/searchable,


we need to add the relevant indexes. For now,
we are only adding a queryable index. Click Edit
Indexes and add queryable index for all fields. Post
addition of indexes, the screen should look like
Figure 2-11.

Figure 2-11. iCloud Dashboard Indexes Screen

24
Chapter 2 CloudKit Overview

Figure 2-12 shows all system and custom fields of Book data type.

Figure 2-12. iCloud Dashboard Book Record Type ScreenFigure

We are now ready with the initial table definition. Next, we will build
a screen to save a book record to the database. Please note, for queries to
work, we need to make the system field recordName queryable.

Summary
In this chapter, we learned about the fundamentals of CloudKit. We also
explored how to create a CloudKit-aware iOS App, and the steps we need
to follow to configure an iOS-native cloud-based database. Toward the end
of the chapter, we learned about CloudKit Dashboard, a convenient, user
friendly way to manage necessary CloudKit functions.
Now that we have covered the fundamentals of Swift programming, in
the next chapter, we will start building our Book Tracker App basic version.

25
CHAPTER 3

Core Swift Concepts


This chapter will provide all the core swift concepts you need to know to
make your Book Tracker App. Please note the list is not intended to be
exhaustive, but as a comprehensive reference guide to help you create
your App.

Variables
Learning to define and use variables are basics of Swift programming. We
are going to focus on the following variable definition types. This is not an
exhaustive list, but the important ones that we will need to develop our
App using the Swift language.

Let and Var


Variables are containers which store values in transient state during the
execution of a program. To define a variable, we must either use “let” or
“var” to let Swift know, a) a variable is defined and, b) the type of the value
the variable must store. Use “let” when the variable value is not going to
change in the scope of the program, and “var” when it will change and will
require reassignment of a new value to the variable during the execution
of the program. It is a good practice to use “let” if the value is not going to
change. Examples

let name:String = ""

© Shantanu Baruah and Shaurya Baruah 2023 27


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_3
Chapter 3 Core Swift Concepts

In this example, we define a variable called “name” of type String


which has an empty String

Basic Types
Types define what value a variable should store. The key types which we
will be mostly using in our Book Tracker Apps are

Int or Int32 or Int64: This stores Integer value


String: String literals
Bool: a true or false or 1 or 0
Double: represent 64-bit floating-point number

Types always come after the variable name separated by colon (“:”)
Please note if the type is not defined, then the variable automatically
assumes the type based on the value assigned. For example, in the
following line, the title variable will be a String variable, which the system
deciphers based on the string literal provided in the quotes.

let title = "This is a test string"

Classes, Structures, and Objects


Classes and structures are a logical collection of variables and methods,
which encapsulate complexities and represent a meaningful blueprint
of an object. Consider, for instance, you want to define a book in our
program. A book can have a book name, author name, and genre. Instead
of defining three variables, we can define a class which can contain all the
three basic types. The difference between a Class and a structure is how it
is referenced.
Structs are value types, while classes are reference types.

28
Chapter 3 Core Swift Concepts

To use a class or a struct, we need to first define it and then create


an object. There can be many objects for a class or struct. When we pass
around an object, for class, we always pass around object reference,
whereas for struct, we pass value. This is an important point, so if you want
to change the same object value across the program, class is the way to go;
otherwise, struct is a safer bet as it is thread-safe and there is no additional
burden we need to carry to do synchronization or dirty value checking.
Class
The syntax of class definition is as follows:

class <name of the class>:<class Type>{


var <variablename>:<type>!
var <variablename>:<type>!
...
}

Example

class Book:NSObject{   //Book is the class name of type


NSObject
var bookname:String!  //variable bookname of string type ! sign
forces value
var authorname:String! //variable authorname of string type
var genre:String! //variable genre of string type
}

NSObject is the native root class from which every subclass is inherited.
Once the class is defined, we need to define the object using var or let,
as explained in the following:

var book = Book()

notice the constructor () after the class name to instantiate the class as
an object which is now referred as book.

29
Chapter 3 Core Swift Concepts

Structs
The syntax of struct definition is as follows:

struct <name of the class>{


var <variablename>:<type>!
var <variablename>:<type>!
...
}

Example

struct Book {   //Book is the struct name. Notice there is no


object inheritance required
var bookname:String!  //variable bookname of string type ! sign
forces value
var authorname:String! //variable authorname of string type
var genre:String! //variable genre of string type

Array
Arrays are structures to hold multiple values of a particular kind of
element. Swift allows a single and multidimensional array of any type
(basic types, classes, or structures). In our Book Tracker App, we will need
a book array to hold all the book names that the user has read (a one
dimensional String Array) or a book details array holding book names,
author, and genre (a class object array).
How to define Arrays

Var <nameof the array>:[Type] = [Values optional ]


var bookName:[String] = ["Xcode Development", "iPhone App
Coding" ]

30
Chapter 3 Core Swift Concepts

bookName array is of type string, which is initialized with all two


book names.

var author:[String] = []

author array is of type string, which is initialized blank. Value can be


assigned later.

class Book:NSObject{
       var bookname:String!
        var authorname:String!
        var genre:String!
        var status: String!
    }
var books:[Book] = []

books variable is an array of type Book class, which can hold multiple
Book details (name, author, genre, and status)
Accessing an Array

for <item> in < class or struct array name>{


print(item.<class or struct variable name>)
}

This for loop as follows will print all book details

for book in books{


print(book. bookname)
print(book. authorname)
print(book. genre)
}

31
Chapter 3 Core Swift Concepts

Scope
Swift follows the Object-Oriented programming principles for object
visibility.

• Program level: If an object is defined outside of a class


will be visible at program the level.

• Class level: An object when defined within a class, all


methods in the class will have access to it.

• Function level: An Object defined in a method is


accessible and visible only at the method level.

• Loop or conditional block: An object defined within


a loop, or a conditional block, is only accessible in the
scope of the loop or block. If there are nested loops or
conditional blocks, the variable defined in the outer
loop, or the conditional block, is visible in the inner
loops. The reverse is not true.

• private: When a variable is defined private, it is only


accessible to the visible functions and methods of the
object. Inherited classes of the parent class, where the
private variable is defined, will not have access to the
private scope variable.

• public: If not qualified as private by default, the scope


is defined as public.

Example

var global:Int = 1
class Scopetest:NSObject{
........
var classlevel:Int = 2
private var classlevel2:Int = 3

32
Chapter 3 Core Swift Concepts

func firstFunction() {
var first:Int = 4
      print(global)  //this will print the value 1
print(classlevel)  //this will print the value 2
print(classlevel2)  //this will print the value 3
print(first)  //this will print the value 4
if(first < 1){
var second:Int = 5
}
print(second) - second is not visible as it is defined in the
conditional block
}
func secondFunction() {
      var second:Int = 6
print(global)  //this will print the value 1
print(classlevel)  //this will print the value 2
print(classlevel2)  //this will print the value 3
print(first)  // error  - first scope is restricted to
the  firstFunction
print(second)  //note this will print 6 not 5
}
}

class  ScopeTest2:ScopeTest{
....
func printValues(){
print(global)  //this will print the value 1 as this a program
level value
print(classlevel)  //this will print the value 2 as it is a
class level variable and ScopeTest2 is an inherited class
print(classlevel2)  // error – private variable can only be
accessed by the class

33
Chapter 3 Core Swift Concepts

print(first)  // error  - first scope is restricted to


the  firstFunction
print(second)  // error  - first scope is restricted to
the  secondFunction or the conditional block
}
}

In summary, scope is of three types:

• Global Scope: this will be at program/App level. All


Classes in the product will have access to any global
variable.

• Class Scope: the scope of this variable is limited to the


scope of the class

• Function Scope: any variable defined at the function is


only accessible within that function.

When a variable is defined as private, it is available within the scope


where it is defined for that class. Any inherited class will not have access
to any private variable. Like variable functions can be defined as private or
public. Same rules apply for functions as well.

Functions
The concept of modular programming highly encourages writing logical
code in self-contained blocks. This helps in not only getting the code more
modularized, hence readable, but also in case if the same block of code is
required for execution multiple times, we can define it once and use it by
just calling the function. A Swift function can be
• Simple block of code with no input or output
parameters. When the function is called, it executes the
code within its body.

34
Chapter 3 Core Swift Concepts

• Block of code with input parameters. This type of


function accepts certain input parameters, which the
method will use to perform the required function.

• Function with both input and output parameters.


Though a function can take multiple parameters, but
the return is always one type.

Example

Class BookViewController:UIViewController{  //Class
BookViewController is of type UIViewController System class
override func viewDidAppear(_ animated: Bool) {  // notice
override, we are overriding the parent function viewDidAppear
                        // it is taking a bool value in a
variable called animated
addNumbers() //  it will print "Number is 3"
addNumbers(firstNo: 20, secondNo: 30) //  it will print
"Number is 50"
              // notice it is mandatory to provide parameter
name when _ is not used
     before the parameter name in defining the function
let finalNo = addNumbers(2, 30) //  this function return 32
                 // notice the parameter name is not required
as the parameter has _ before the name
                 // notice finalNo type is not defined. Swift
automatically typecast to the required type
print("Number is \(finalNo)") // it will print 32
}
private func  addNumbers(){ // function not taking any inputs
not returning any value
let firstNo:Int = 1
let secondNo:Int = 2

35
Chapter 3 Core Swift Concepts

print("Number is \( firstNo + secondNo)")


}
private func  addNumbers( firstNo:Int, secondNo:Int){ //
functions takes two parameters
print("Number is \( firstNo + secondNo)")
}
private func  addNumbers(  _ firstNo:Int, _ secondNo:Int) ->
Int{ // functions takes two parameters and returning an integer
return firstNo + secondNo
}
}

Beautifying Strings Using NSAttributedString


Swift text field, label, and button UI Objects can display text with the .text
property. If we need to beautify a portion of text with different font, color,
size, or kerning, there is another property called .attributedText. The
following section shows how we can define a font object, a paragraph style,
and assign it to a label object.

let label = UILabel() // defining a label UI object


var font = UIFont.systemFont(ofSize: 16, weight: .bold) //
defining a font variable with size 16 and with weight bold
var paragraphStyle = NSMutableParagraphStyle() // define a
variable of NS Mutable paragraph Style object
paragraphStyle.alignment = .center //making the paragraphStyle
variable center alignef
paragraphStyle.firstLineHeadIndent = 5.0 //setting the
paragraph’s first line top indent to 5.0 pixel
let attributes: [NSAttributedString.Key: Any] = [  // we are
setting NS Attribute String object to the defined font and
paragraph Style

36
Chapter 3 Core Swift Concepts

           and color of the text as black


.font: font,
.foregroundColor: UIColor.black,
.paragraphStyle: paragraphStyle
]
var attributedLabel = NSAttributedString(string: "Test Label",
attributes: attributes) // assigning a text label with the
defined attributes
label.attributedText = attributedLabel //assigning the NS
Attribute string to the label attributedText

Life Cycle Methods


In our Book Tracker App, we have extensively used View controllers for all
the display screens. Every view controller has a set of lifecycle methods,
which are important to learn to understand what we need to do when the
view is loaded, or when the view disappears. Figure 3-1 provides a quick
view of the important methods and the time when they are triggered.

37
Chapter 3 Core Swift Concepts

Figure 3-1. Life Cycle Method Lifecycle

• viewDidLoad: This method is called when the view is


loaded. Consider calling this function when you want
to call this function once. All the display setup function,
data query for display can be called in this method.

• viewWillAppear: This method is called before the view


is visible. Consider using this function for activities
you want to call every time the view controller is
getting loaded. For example, if we need to hide fields
or manipulate certain actions, this is the right place
to do it.
• viewDidAppear: This method is called after the view is
visible. Consider using this function for activities you
want to perform after the view controller is loaded. For
example, if we need to query for all books every time

38
Chapter 3 Core Swift Concepts

the view loads because we might have deleted/added a


book, this is a good place to call such a function.

• viewWillDisappear: This is called before the view


is removed from the view hierarchy. Unloading
animations, hiding the keyboard, and canceling
network requests are some of the actions you can
perform here.

• viewDidDisappear: This is called when the view


disappears. Stopping listening to notifications is a good
example of activities we can perform here.

• viewDidDisappear: When the view controller is


removed from the hierarchy, this function is called.
Stopping to listen to a notification is a good example of
a task that can be done in this method.

Type Casting
While writing Swift programs, we will have occasion when we have
downcast a variable from its superclass to a subclass. This could be both
for native data types (for example, converting an Int64 to Int, or a Double
to Int) or for custom data type. For custom data type, we will take an
example of our Book Type to show how it works

Step 1: Book Class


Book class has a variable to store the book name and an init method to
initialize the variable

39
Chapter 3 Core Swift Concepts

class Book {
var name: String
init(name: String){
self.name = name
}
}

Step 2: Fiction Class Subclass of Book Class


Fiction class is a subclass of Book class; it has a variable to store the fiction
genre name and an init method to initialize the genre, which also calls the
Book super class to initialize the book name.

class Fiction:Book {
var genre: String
init(name:String, genre: String){
self.genre = genre
super.init(name: name)
}
}

Step 3: Non-Fiction Class Subclass of Book Class


Non-Fiction class is a subclass of Book class. It has a variable to store the
non-fiction genre name and an init method to initialize the genre, which
also calls the Book super class to initialize the book name.

40
Chapter 3 Core Swift Concepts

class Nonfiction:Book {
var genre:String
init(name:String, genre:String){
self.genre = genre
super.init(name: name)
}
}

Step 4: Define an Object of Type Book Class


We are defining an array of books called library and initializing it with
four books

let library = [ Fiction(name: "As You Like It", genre:


"Classic"),
Nonfiction(name: "The Splendid and the
Vile", genre: "History"),
Fiction(name: "To Sir With Love", genre:
"Educational"),
Nonfiction(name: "Feeling Good", genre:
"Cognitive Therapy"),
]

Step 5: Method to Count Fiction or Non-fiction Class


Looping through the library to count all fiction and nonfiction books –
notice the downcast from book to Fiction and Non-Fiction using the “as”
qualifier.

41
Chapter 3 Core Swift Concepts

func getBookCounts(){
var fictionCount = 0
var nonFictionCount = 0
for book in library{
if book in Fiction{
fictionCount += 1
}else if book in Nonfiction{
nonFictionCount += 1
}
}
print("Total Fiction books are \(fictionCount) and the
Total Non Fiction books are \(nonFictionCount)")
}

Loop Controls
Swift provides many ways to loop through an array of objects. We will go
through the most important ones.

For-In Loops
For-in loops are used to iterate over items of an array.

42
Chapter 3 Core Swift Concepts

let library = [ Fiction(name: "As You Like It", genre:


"Classic"),
Nonfiction(name: "The Splendid and the
Vile", genre: "History"),
Fiction(name: "To Sir With Love", genre:
"Educational"),
Nonfiction(name: "Feeling Good", genre:
"Cognitive Therapy"),
]
func getBookCounts(){
for book in library{
print(book.name)
}
}

For In-Loop with Range


This is used to go through a range of numbers in incremental orders. Let
us assume we need to go through all the books we have in the library and
display some book-related data. Currently, we are printing the number.
The following function will print 4 rows with values in incremental order 0

1
2
3

43
Chapter 3 Core Swift Concepts

let library = [ Fiction(name: "As You Like It", genre:


"Classic"),
Nonfiction(name: "The Splendid and the
Vile", genre: "History"),
Fiction(name: "To Sir With Love", genre:
"Educational"),
Nonfiction(name: "Feeling Good", genre:
"Cognitive Therapy"),
]
func demonstrateForCloseRange(){
for count in 0..<library.count{
print(count)
}
}

While Loop
While loop is executed till a condition is met. In the following example,
we will print the books in the library count is equal to 2 (the count
starts from 0)

44
Chapter 3 Core Swift Concepts

let library = [ Fiction(name: "As You Like It", genre:


"Classic"),
Nonfiction(name: "The Splendid and the
Vile", genre: "History"),
Fiction(name: "To Sir With Love", genre:
"Educational"),
Nonfiction(name: "Feeling Good", genre:
"Cognitive Therapy"),
]

func whileDemostration(){
var count = 0
while count < 2 {
print(library[count].name)
}

count += 1
}

UI Color
This chapter introduces the UI Color feature in Swift. The following
sections will help you to understand the UI Color Object in greater detail.

Overview
UI Color is an object that stores color data and sometimes opacity. UI
Color inherits from the NSObject, the root class for most Swift hierarchies.
UI Color helps to bring color to your App, allowing you to customize your
App's appearance, communicate with the user, and help them to visualize
your data. Make sure to read and understand section two, Guidelines for UI
Color, before implementing color into your App. UIColor includes many

45
Chapter 3 Core Swift Concepts

different class prosperities that create colors such as blue, green, and more.
You can create more unique shades by changing the values of the colors
through RGB, hue, and saturation.

Guidelines for UI Color


Apple provides a Human Interfaces Guidelines for using UI Color in your
Apps. The following is a summarized version of the five most important
notes from the Apple Guidelines.
First, you must not completely rely on color in your App for
differentiating between objects or to deliver important information.
Users with colorblindness or with disabilities may find it difficult to use
your App if you do so, so it would be more beneficial to communicate
your information in a second way, in addition to using color. This could
be done in many ways, such as through text labels, shapes, or images.
Color is a powerful tool; however, one must not solely rely on color when
producing an App.
Second, make sure to be smart about how you implement colors
into your App. Certain colors affect the mood or feelings of the user, for
example, dark colors such as red may make the user anxious while calm
and warm colors would be soothing to the user. A simple change in color
may have a huge impact on how the user interacts with your application.
Colors like red would be beneficial when making a warning or cancel sign.
If you use red elsewhere in the App, in addition to the warning sign, it may
be less effective.
Third, don’t overcomplicate your App with too many colors. For the
optimum user experience, it is best to keep your color palette sampler with
lighter colors that are consistent throughout the App rather than mixing
too many colors that are not effective with each other. If you look at one of
the apps on your phone, you will notice how each App has a unique color
scheme that is consistent throughout the App.

46
Chapter 3 Core Swift Concepts

Fourth, important objects in your App should stand out. For example,
if you are writing an App that has a check bar, you must make sure that
the color you choose for the check bar stands out and is not hidden by the
other colors in your App. Don’t use the same color for interactive and non-­
interactive elements to avoid any confusion from the user.
Fifth, it is always better to have alternate color options for the user.
Allow the user to choose which color he/she wants to use the App in by
making the option in settings or elsewhere in the application. Also create
two accent colors for each color option provided so that the App looks
good in both dark mode and light mode.
Keep these notes in mind when making your Swift App.

System Colors
When implementing colors into your App, it is best to use an API. Never
hard code system color values into your App as iOS offers a range of system
colors already in the system. Figure 3-2 provides a table of the UI Colors
in Swift

47
Chapter 3 Core Swift Concepts

EĂŵĞ ^ǁŝŌh/W/
ůƵĞ ƐLJƐƚĞŵůƵĞ
ƌŽǁŶ ƐLJƐƚĞŵƌŽǁŶ
LJĂŶ ƐLJƐƚĞŵLJĂŶ
'ƌĞĞŶ ^LJƐƚĞŵ'ƌĞĞŶ
/ŶĚŝŐŽ ƐLJƐƚĞŵ/ŶĚŝŐŽ
DŝŶƚ ƐLJƐƚĞŵDŝŶƚ
KƌĂŶŐĞ ^LJƐƚĞŵKƌĂŶŐĞ
WŝŶŬ ƐLJƐƚĞŵWŝŶŬ
WƵƌƉůĞ ƐLJƐƚĞŵWƵƌƉůĞ
ZĞĚ ƐLJƐƚĞŵZĞĚ
dĞĂů ƐLJƐƚĞŵdĞĂů
zĞůůŽǁ ƐLJƐƚĞŵzĞůůŽǁ
'ƌĂLJ dŚĞƌĞĂƌĞƐŝdž
ƐŚĂĚĞƐŽĨŐƌĂLJ

ƐLJƐƚĞŵ'ƌĂLJ
ƐLJƐƚĞŵ'ƌĂLJϮ
ƐLJƐƚĞŵ'ƌĂLJϯ
ƐLJƐƚĞŵ'ƌĂLJϰ
ƐLJƐƚĞŵ'ƌĂLJϱ
ƐLJƐƚĞŵ'ƌĂLJϲ

Figure 3-2. Swift System Colors

Dynamic System Colors


iOS has semantically defined system colors in addition to the general color
types. These colors automatically adapt to light or dark modes, and each
color always has its own purpose. These colors may be used for backgrounds,
or for foreground, such as for labels, buttons, and others. There are two types
of background colors, including system and grouped. Each of these types
contains its own variants to convey a hierarchy of information.

48
Chapter 3 Core Swift Concepts

Getting System Colors


When you want to use a specific shade or color in your program, you can
use the standard color objects. There are fixed colors such as red, blue,
black, among many others, and you can create your own shades by using
different hues and RGB. You can also alter the opacity of the color in your
program.

Syntax
In the following example, we are setting the color of the View Controller
background color to white.

Define Using System Defined Color


view.backgroundColor = .white

Define Using RGB


We can find the RGB of any color through any publicly available color
picker or using Swift Color Picker in the Main Storyboard. The number
ranges from 0 to 255 and needs to be divided by 255 to get the accurate CG
Float number. Alpha is used for transparency. Any value less than 1 will
make the color opaque.

view.backgroundColor = UIColor(displayP3Red: 255/255, green:


255/255, blue: 255/255, alpha: 1)

49
Chapter 3 Core Swift Concepts

CG Color
Swift allows access to the fundamental Core Graphics for layering,
transforming, or coloring. For instance, for an UI Button, we can access
its core layer and color the border with a defined color. Since it is the core
graphics, we need to use CG Color instead of UI Color. We can either use a
CGColor graphics Init or convert a UI Color to CGColor.

let bookLabelButton:UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints =
false
button.contentEdgeInsets = UIEdgeInsets(top: 5, left:
5, bottom: 0, right: 0)
button.contentHorizontalAlignment = .center
button.contentVerticalAlignment = .center
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont.
boldSystemFont(ofSize: 16)
button.clipsToBounds = true
button.titleLabel?.numberOfLines = 5
button.titleLabel?.lineBreakMode = NSLineBreakMode.
byWordWrapping
button.backgroundColor = UIColor(displayP3Red: 89/255,
green: 140/255, blue: 217/255, alpha: 1)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5

50
Chapter 3 Core Swift Concepts

button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
return button
}()

Note that the border color is set in two ways:, one using system
color and the other using UIColor RGB. Also, notice the typecasting to
cg color.

UINavigationBar
UINavigationBar in most of the cases will be used as part of a Navigation
controller to navigate between screens (Figure 3-3). In such cases, the UI
Navigation creates a navigation item stack and uses system-defined push
and pop to navigate between the screens in the stack.

Figure 3-3. UI Navigation Bar Structure

A UI Navigation Bar typically appears at the top of the screen and has
three parts, a display title, a left Bar Button Item, and a right Bar Button
Item. The buttons can be used to perform any action or function and
it behaves like a UI Button. The events and properties of UI Button are
inherited by the UI Bar Button Item.

51
Chapter 3 Core Swift Concepts

Some important properties of the UI Navigation Bar are listed in


Table 3-1.

Table 3-1. UI Navigation Bar Key Properties


Background Setting up a color for the navigation Bar
Color let navigationBar = UINavigationBar()
Defined the navigation bar variable
navigationBar.backgroundColor = UIColor.init(red: 171/255, green:
189/255, blue: 217/255, alpha: 1)
setting the background color using UIColor RGB values.
Setting Up Navigation Bar title, right button and left button can be set through
Navigation UINavigationItem
Items let navItem = UINavigationItem(title: “Your Title”)
defining a variable of type UI Navigation Item. The constructor takes
the Title of the bar as an input parameter.
let resetSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 15, weight: .black)
let resetImage = UIImage(systemName: “arrowshape.turn.up.left.
fill”, withConfiguration: resetSymbolConfiguration)
let resetItem = UIBarButtonItem(image: resetImage,
landscapeImagePhone: resetImage, style: .plain, target: nil, action:
#selector(resetButtonAction))
navItem.leftBarButtonItem = resetItem
The preceding line of code defines an Image, assigns configuration
elements, and then the image is assigned to the left Bar Button of
the Navigation Item. Similarly, we can assign another image to the
right-hand bar item.
(continued)

52
Chapter 3 Core Swift Concepts

Table 3-1. (continued)


Please note the definition of the method resetButtonAction. When
the user touches the left button, this custom method will be called.
navigationBar.setItems([navItem], animated: false)
Finally, we assign the navItem Array to the navigation bar object,
which will display it on the screen.

UIImage and UIImageView


Images are often needed for better interaction and enhancing visual
appeal. Swift UI Image objects can display all kinds of images (the
recommended types are PNG and JPEG). UIImage contains the data of the
image and UIImageView is a custom view to display the image referenced
by the UIImage object. We can put an UIImage on other UI objects such as
Button, Navigation Bar, and Segmented Controls.

How to Create an UIImage View


let image = UIImage(systemName: "book ")
let imageView = UIImageView(image: image!)

In the preceding example, we are creating an UIImage object using a


system image (iOS offers a collection of predefined images that can be used to
display images. You can look them up in the Main StoryBoard by placing a tab
bar button object on the default view controller and in the Attribute Inspector
Property Page, use the Image drop-down to look for all available images.)
The UIImage object is stored in a variable image, which in turn is
assigned to the UIImageView.
Please note UIImage has many constructors; the one we used in the
previous example creates the image object using System defined images.
Some of the other available constructors are created from content of a File
and referring to an image from the Asset Repository.

53
Chapter 3 Core Swift Concepts

Image is displayed after it is added to the display view or to the relevant


UI object.

UI Image View Key Properties


Table 3-2 provides details of the UI ImageView key properties

Table 3-2. UI ImageView Key Properties


Alignment to We are using the variable defined previously for UI Image View
Center object - imageView
imageView.center = view.center
This will align the image view to the center of the view
Making the We are using the variable defined previously for UI Image View
image outline object - imageView
to be curved imageView.layer.borderWidth = 1

imageView.layer.cornerRadius = 5
imageView.layer.borderWidth = UIColor.blue.cgColor
when applied, it will create a rounded corner like the image that
follows:

(continued)

54
Chapter 3 Core Swift Concepts

Table 3-2. (continued)

To get this effect, we need to access the visual content of the


object. This we can do by accessing the layer property of the image
view object. We are setting the border width to 1 pixel and corner
radius to 5 to get the rounded corner effect, and the color of the
border to blue. As we are accessing the visual content, we need to
convert the UI Color object to native CG color object.
Please note, this can be applied to any visual object in Swift.

Image Image View can scale the image based on its size. In the following
Size and code, we are creating a frame object and setting its height, width,
positioning X and Y coordinates, and assigning it to the frame property of our
imageView Object
var imageViewFrame = imageView.frame
imageViewFrame.size.width = 200
imageViewFrame.size.height = 200
imageViewFrame.origin.x = 0
imageViewFrame.origin.y = 0
imageView.frame = imageViewFrame
Please note this can be applied to any UI visual object in Swift.

Key Methods
UI Image View does not offer any default method; if we need to set up a
click event, we need to use the Add Gesture Recognizer. Please note, this
can be applied to any UI visual object in Swift.

55
Chapter 3 Core Swift Concepts

let tapGestureRecognizer = UITapGestureRecognizer(target:


self, action: #selector(imageTapped(tapGestureRecognizer:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizerReminder)

The first line defines a tap gesture, and it defines a method imageTapped which will
be called when the image will be tapped
The second line ensures user can interact with the imageView object
And the third line adds the gesture to the imageView object
In the custom defined method imageTapped, we can define the action we want to
perform.

UITextField
Text Field is one of the most common UI Objects we will be using while
creating iOS Apps. We use UITextField to gather inputs from users. Users
can enter values using the onscreen keyboard, which is configurable to
display the right keys for easier data inputs (example, the display keyboard
can be a numeric pad to restrict users to only enter digits for a phone
number text field). In our App, we will use Text Field to input values such
as Book Name, Author Name, and Search String.

How To Create an UITextField


let bookTextField = UITextField()

In the preceding example we created a UITextField named


bookTextField

UITextField View Key Properties


Table 3-3 provides details of the UI TextField key properties

56
Chapter 3 Core Swift Concepts

Table 3-3. UI TextField Key Properties


Placeholder Placeholder text is a text prompt that helps the user to know what
Text is required to be entered. The text disappears as the text field gets
focus
bookTextField.placeholder = “Enter Book Name”
Text Color We can set the color of Text with the following line of code
bookTextField.textColor = .white
since Text Color takes UIColor “.” Refers to UIColor there are
certain system-defined colors. If you want to set a custom UIColor,
we can use the UIColor RGB constructor.
Background We can set the background of the Text Field using this property
Color bookTextField.backgroundColor= . .darkGray
since Text Color takes UIColor “.” Refers to UIColor there are
certain system-defined colors. If you want to set a custom UIColor,
we can use the UIColor RGB constructor.
Enabled The Enabled property takes a Boolean value. When set to false, the
Text Field becomes read only.
bookTextField.isEnabled = false
Hidden The Hidden property takes a Boolean value. When set to false, the
Text Field becomes invisible.
bookTextField.isHidden = false
Resign First If we need to dismiss the keyboard, we can call this method on
Responder the Text Field
bookTextField.resignFirstResponder( )
Focus First If we need to make the keyboard appear, we can call this method
Responder on the Text Field
bookTextField.becomeFirstResponder( )

57
Chapter 3 Core Swift Concepts

Key Methods
There are three events that come very handy in dealing with UI Text Field.
These events can be attached to the text field using the addTarget Method.
The methods are explained as follows:

1. Editing Did Begin

This event is called as soon as the user starts typing in the Text Field.
Operations such as setting the border color before user types is a good
example of operations we can do in this event.

bookTextField.addTarget(self, action:
#selector(bookTextFieldDidEnd), for: .editingDidBegin)

In the preceding line, we added the event .editingDidBegin to the


bookTextField Target using the addTarget method. #selector helps in
defining the custom method bookTextFieldDidEnd, which will be called
when the editing begins.

2. Editing Did End

This event is called when focus moves away from the Text Field.
Operations such as resetting variables or capturing entered data are good
examples of operations we can do in this event.

bookTextField.addTarget(self, action: #selector(bookTextFieldDi


dBegin), for: .editingDidEnd)

In the preceding line, we added the event .editingDidEnd to the


bookTextField Target using the addTarget method. #selector helps in
defining the custom method bookTextFieldDidBegin, which will be called
when the editing begins.
3. Editing Did Changed

58
Chapter 3 Core Swift Concepts

This event is called every time a user starts typing. Operations such as
checking the entered value or formatting input fields based on data entry
(like phone formatting), are a good example of operations you can do in
this event.

bookTextField.addTarget(self, action: #selector(bookTextFieldEd


itingChanged), for: .editingChanged)

In the preceding line we added the event .editingChanged to the


bookTextField Target using the addTarget method. #selector helps in
defining the custom method bookTextFieldEditingChanged, which will be
called when the editing begins.
The custom method defined in all the three events takes a UITextFiled
as an input, which gives access to the text field. For example

@objc func bookTextFieldEditingChanged(textfield: UITextField){


}

The textfield variable will give access to the current text filed.

4. Text Field Should Return

If we need to capture the event when the Keyboard Return key is


touched, we will need this method. Unlike the other three events, this
event is not part of the Text Field available list of events. To enable this, we
need to do the following:

• Inherit the Delegate UITextFieldDelegate for the class.

• During the view initialization (for example,


viewDidLoad method) set the text field on which you
need to enable the return key event, set the delegate to
self bookTextField.delegate = self

59
Chapter 3 Core Swift Concepts

• Implement the following method:

func textFieldShouldReturn(_ textField: UITextField)


-> Bool{
        textField.resignFirstResponder()
}

In the preceding example, when the return key on the keyboard is


pressed, the keyboard will disappear.

UIAlertController
Alert Dialogs are an important part of iOS SDK, which, based on user
interaction with the App, we can display information and prompt users to
take quick and appropriate action. Alerts come in very handy to confirm
user actions to ensure it was not done accidently. Example, a record
getting a confirmation from a user delete action is invoked. In this section,
we learn how to create an alert, display it and, based on user response, take
appropriate action.

Defining an Alert
AlertViewController has a constructor which takes the following
arguments:

• Title: it takes a string which is the title of the Alert

• Message: message Is a string literal which is displayed


as the main information string

• Preferred Style: it could be either an alert or an action


sheet (a list of actions that user can take)

let alertController = UIAlertController(title:


"Warning", message: "Book Name be Blank",
preferredStyle: .alert)

60
Chapter 3 Core Swift Concepts

Figure 3-4 shows how the system will show the alert screen.

Figure 3-4. Save Alert Popup Screen

Alert Action and Working with User Action


Next, we will define the action buttons. When a user clicks on the button, a
particular set of actions will be executed in the button action block.

let okAction = UIAlertAction(title: "OK", style: UIAlertAction.


Style.default) {
                        UIAlertAction in
//Do Something
}
let okCancel = UIAlertAction(title: "Cancel", style:
UIAlertAction.Style.default) {
                        UIAlertAction in
//Do Something
}

We have defined two buttons using the UIAlrertAction constructor. The


first one is an “OK” button and the second one is a “Cancel” button. In the
body of the method, we can define what we want to perform when the user
selects the button.

61
Chapter 3 Core Swift Concepts

Next, we will add the actions to the previously defined


UIAlertController.

alertController.addAction(okAction)
alertController.addAction(cancelAction)

Alert Display
So far, we have defined the Alert Controller object, defined the action
buttons, and added it to the Alert Controller. Now we are ready to display
the alert. Use the following code to achieve the same:

self.present(alertController, animated: true, completion: nil)

self refers to the View Controller and we are using the current view’s
present method to display the alert.

UITableView
Table Views are one of the most important UI Objects in the iOS toolkit. A
table view displays a collection of vertical rows where each row contains
a collection of information that is displayed on the screen. The table view
is scrollable, allowing users to scroll through a long list of content. Table
view also allows grouping of content and shows the content in separate
sections. Each displayed row is a table view cell, which can be customized
to display any UI Object. In this section, we will learn to create tables using
code. We can also create a table view using the StoryBoard.

Creating a Table View


private let tableView:UITableView = {
        let tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.backgroundColor = .white

62
Chapter 3 Core Swift Concepts

        tableView.isScrollEnabled = true
        return tableView
    }()

The preceding line of code will create a table view with a white
background color and scroll enabled to allow the user to scroll up or down
the record set.

Delegate Methods
To implement a table-view, our class needs to inherit two delegates as
explained in the following:

• UITableViewDelegate: UITableView Delegate is


required to manage the selection, view, header and
footer, and cell definition of table views. Essentially,
the presentation of the table view is managed by this
delegate.

• UITableViewDataSource: UI Table View Data source


responds to data-related requests from the table.
All data-related queries for the table in the App is
controlled by Table View Data Source delegate.

• We can use the following line of code to implement the


delegates.

class viewController: UIViewController,


UITableViewDelegate, UITableViewDataSource {

63
Chapter 3 Core Swift Concepts

Please note, the table view delegates require a mandatory


implementation for two system-defined methods. The first one is Number
of Rows in the table view, and the second one is Cell for Row At, to display
the records. We will learn this in the Displaying the Table section as
follows.

Setting the Table


The next step is to make the view aware about the table view. During the
view initializing method (for example, in the viewDidLoad method), we
need to set the following:

• Letting the class know that the table view we defined


before confirms to the defined delegates.

tableView.delegate = self
tableView.dataSource = self

• Register the table with the type of Table Cell and give
it a reference name. Cells are used to display data in
the table. The default system-defined table cell name
is UITableViewCell. UITableViewCell has two labels to
display text. A main data label and a detailed text label.
We can also customize the cell to display any UI Object
component.

tableView.register(MotivationTableViewCell.self,
forCellReuseIdentifier: "all")

Drawing the Table


Now that our table is defined, next we will display it on the screen. We will
use the custom method we have defined and explained before named
setFieldLayout in our custom class CommonFunctions (see Part I to learn
about this function).

64
Chapter 3 Core Swift Concepts

view.addSubview(tableView)
commonFunctions.setFieldLayout(mainField: tableView,
constraintField: view!, topAnchor: 60, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 460)

The preceding line of code can be put in a custom-defined class,


which can be called in the view-initializing method (for example, in the
viewDidLoad method). View is the current view.

Displaying a Table
We will discuss three system-defined functions which will help us to show
the table and relevant data on the screen.

• Number of sections

Number of sections helps us to divide tables into logical groups of


data sets. For example, if we need to show Books by the status of reading
(“Currently Reading”, “To Be Read”, and “Read”), we will have three groups.
Number of sections in this case will be three.

func numberOfSections(in tableView: UITableView) -> Int {


        return <integer number>
}

• Number of Rows

This is a mandatory function and is required when Table Delegates


are inherited. The function lets the table know how many rows are to
be displayed for each group. (If there is no grouping, one section is
considered as a single group).

func tableView(_ tableView: UITableView, numberOfRowsInSection


section: Int) -> Int {
        return <integer number>
}

65
Chapter 3 Core Swift Concepts

• Cell for Row At

This is also a mandatory function and is required when Table


Delegates are inherited. The function is repeated by system for each of the
displayed row on the screen. Each row has a UI Table Cell View used for
displaying data.

func tableView(_ tableView: UITableView, cellForRowAt


indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(with
Identifier: "all", for: indexPath)
cell.textLabel?.text = ""
return cell
    }

The dequeuereusablecell is important to note when we create a cell.


It repurposes displayed cells to optimize memory, as cells are costly
resources.

Selection of Row
If we need to perform an action upon row selection, the following code will
come in handy. We can perform function like displaying another table or
another view.

func tableView(_ tableView: UITableView, didSelectRowAt


indexPath: IndexPath) {
        <#code#>
}

Refer to Part II to learn how a custom cell can be configured to have a


button and upon event like touch up inside, we can invoke certain functions.

66
Chapter 3 Core Swift Concepts

Table Header
Every section of the table can be assigned a Header view. Header view can
be customized to show any UI Object. In the following example, we will
customize the header view to show a UI label object.

func tableView(_ tableView: UITableView, viewForHeaderInSection


section: Int) -> UIView? {
        let headerView = UIView.init(frame: CGRect.init(x: 0,
y: 0, width: tableView.frame.width, height: 50))
        headerView.backgroundColor = UIColor.init(red: 245/255,
green: 245/255, blue: 245/255, alpha: 1)
        let label = UILabel()
        label.frame = CGRect.init(x: 0, y: 0, width: headerView.
frame.width, height: headerView.frame.height)
        label.text = "Motivation Technique"
        label.textColor = .systemBlue
        label.font = UIFont.boldSystemFont(ofSize: 20)
        label.textAlignment = .center
        headerView.addSubview(label)
        return headerView
    }

The preceding line of code defines a view, headerView, which occupies


the table view width. We are setting the background of the view to the UI
color of our choice. After that, we are defining a label and setting the width
and height of the label frame to the header view height and width. A text,
color, and font and alignment are set for the label, and it is added to the
header view as a sub view. The header view is then returned to display on
the table header view.

67
Chapter 3 Core Swift Concepts

Cell Heights
Cell and header view heights are by default set to 40 pixels. When we
define custom cell and views, we may have to set the height of each cell or
view accordingly.

func tableView(_ tableView: UITableView, heightForRowAt


indexPath: IndexPath) -> CGFloat {
        return <Float>
}

The preceding line defines the height of every row.

func tableView(_ tableView: UITableView,


heightForHeaderInSection section: Int) -> CGFloat {
        return <float>
}

The preceding line defines the height of every section header.

Swiping Function
Table View cell also offers a swipe gesture where we can add other
executable functions. The following code creates a delete swipe action
gesture.

func tableView(_ tableView: UITableView,


trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
-> UISwipeActionsConfiguration? {
        let deleteAction = UIContextualAction(style:
.destructive, title: "Delete", handler: { (action,
view, success) in
          <#code#>
        })

68
Chapter 3 Core Swift Concepts

        deleteAction.backgroundColor = .red
        deleteAction.image = UIImage(systemName: "trash")
        return UISwipeActionsConfiguration(actions:
[deleteAction])
}

The method is a system defined table view function which returns


a Swipe Action Gesture. A deleteAction variable of type UI Contextual
Action type which shows a trash icon on swipe and when user clicks it, the
defined code in the handler will be executed.

Summary
This is the only chapter in the book which introduces the user to core
concepts of coding using the Swift language. This chapter is not exhaustive
by any means, but provides the most important concepts. We learned the
following in the chapter:

• How to define Variables, Classes, Struct, and Objects

• Concepts of storing objects and variables as Arrays

• How to define functions and methods

• Typecasting of Objects

• How to Beautify Strings

• How to Loop through OBjects

• Define core UI Objects - UILabel, UITextField,


UITableView, UIAlertController, UIImage,
UIImageView, UiNavigation, and UIColor

Now that we have learned the core concepts, we will apply them in the
subsequent chapters to create our Book Tracker App.

69
CHAPTER 4

Book Tracker
Basic App Building
The next five chapters are dedicated to creating the Basic version of
the Book Tracker App. We will use the concepts we have learned in the
previous chapter to create our App. In this chapter, we will create the Tab
View Controller for our basic version of the book tracker Application. The
Tab View Controller will help us navigate between three main screens -
Main Display, Adding a Book, and Searching Book.

Setting Up the Tab View Controller


The book tracker App will have a tab bar view controller from where the
user can navigate to three screens:

1. The Display screen: This is the home screen which


will display the list of books by status (Currently
Reading, to be Read, or Reading).

2. The Add Book screen: Using this screen, user can


add books to the digital library.

3. Search Screen: User can search books based on


book name, author, or genre.

© Shantanu Baruah and Shaurya Baruah 2023 71


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_4
Chapter 4 Book Tracker Basic App Building

Let us first set up a Tab View Controller. A Tab view controller is a built-­
in navigator that helps a user to move between tabs.
Follow the following steps to create an application:

• Select App template from the iOS category. Figure 4-1


shows the project template screen.

Figure 4-1. Project Template Selection

72
Chapter 4 Book Tracker Basic App Building

• From the project options, as shown in Figure 4-2,


please provide the following (please change the values
based on your choice).

• Name for your application under “Product Name”


as “MyLibrary”

• Organization identifier – I have provided “SNN”

• Make sure Interface is selected as StoryBoard

• Language is Swift

• And Use Core Data and Cloudkit for data


persistence

Figure 4-2. Project Options

73
Chapter 4 Book Tracker Basic App Building

• From the Project “Signing and Capabilities”, as shown


in Figure 4-3, under Team, use your Apple ID to sign in

Figure 4-3. Project Signing and Capabilities

74
Chapter 4 Book Tracker Basic App Building

• Click the + symbol next to Capability and add iCloud


capability for Cloudkit container functions, as shown in
Figure 4-4.

Figure 4-4. Project Capability Selection

75
Chapter 4 Book Tracker Basic App Building

• Select CloudKit under iCloud to enable cloud data


storage, as shown in Figure 4-5.

Figure 4-5. Project iCloud Settings

Creating a Tab Application


Follow the steps the following to create a Tab based Application:

1. From the Project Navigator on the left, click


Main.Storyboard. This is where you can set up
your screens and Widgets. When you create the
application, a View Controller is created for you
by default. Select the View controller by either
navigating through the storyboard navigation and
clicking on the Item Scene, or by directly selecting
the view controller from the visual display the first
small icon (item) on top of the Screen as shown
below. Figure 4-6 shows the visual builder screen.

76
Chapter 4 Book Tracker Basic App Building

Figure 4-6. Visual Builder Screen

2. We need to make this view controller a Tab View


Controller. To do that from the top Menu, go to
Editor ➤ Embed In ➤ Tab View Controller, as
shown in Figure 4-7.

77
Chapter 4 Book Tracker Basic App Building

Figure 4-7. Tab View Controller Menu Option


3. The screen will change, as shown in Figure 4-8.
Please see the gray Tab Bar Controller.

Figure 4-8. Screen Builder with Tab View Controller

78
Chapter 4 Book Tracker Basic App Building

Our Book Controller will have three Tabs:

• Book Display – To display Books based on the


assigned status

• Add Book – Add a book to the repository

• Search – Search any book based on certain criteria

4. We need to add three view controllers. From the top


bar below the menu, the first button from the left is
the library icon (+). Click it to add a View Controller.
You can search by typing the initial few letters of the
Object. Notice I typed “View Con”. Figure 4-9 shows
the Add Object UI Screen.

Figure 4-9. UI Object Add Screen

5. Drag the two View Controllers (the first view


controller was already available) to the Storyboard.
The Storyboard should look like Figure 4-10.

79
Chapter 4 Book Tracker Basic App Building

Figure 4-10. Updated Visual Builder with the three View Controller

6. For the newly added View Controller, we need to


add a Tab Bar Item. Click on the Library (+) and
search for Tab Bar Item, as shown in Figure 4-11.

Figure 4-11. UI Object Add Screen

80
Chapter 4 Book Tracker Basic App Building

7. Drag the Tab Bar Item to the view Controller. Do it


twice, one for each newly added View Controller.
Note, the first view controller already has the Tab
Bar Item. Figure 4-12 shows the storyboard with all
three Tab View Controller.

Figure 4-12. Screen Builder with Tab View Controller

8. We need to associate the Tab Bar Controller to the


remaining view controller. To do this, click and hold
the control button and drag the mouse from the Tab
Controller to the View Controller. Do it twice for
each of the View Controllers. After dragging, it will
pop up a menu for the Segue. Select Relationship
Segue ➤ View Controllers. (Note: Segues are visual
connectors that allow a view controller to pass data
and present another view controller.) Figure 4-13
shows all three View controllers connected to the
Main Tab Controller.

81
Chapter 4 Book Tracker Basic App Building

Figure 4-13. Screen Builder with Connected Tab View Controller

9. Next, we need to change the Icon for each of the


Tab Bar Items. Select the First View Controller Tab
Bar Item. From the right-hand side menu select the
Attribute Inspector (5th Icon from the left). We will
provide the following details:

• Title as “Display”

• Image – iOS offers many built-in Icons (you can


also add your custom image by creating it in tools
like Adobe Photoshop or GIMP and import it in the
Assets folder of your application) – we will use the
built-in Book Icon.

• Figure 4-14 shows the screens with updated Tab


bar icons.

82
Chapter 4 Book Tracker Basic App Building

Figure 4-14. Screen Builder – Setting Icons

10. Repeat this step for the remaining two View


Controllers as follows:

• Second View Controller Title “Add Book”

• Second View Controller Image “Pencil”

• Third View Controller Title “Search”

• Third View Controller Image “magnifyingglass”

Let us now run the application. For running, you can either run it on
your phone by connecting the phone to the laptop or on a simulator. In
this example, we will run it on an iPhone 12 Simulator. You can select the
simulator from the top bar, as shown in Figure 4-15.

83
Chapter 4 Book Tracker Basic App Building

Figure 4-15. Run Menu Option

After running the simulator application, it should look like in


Figure 4-16.

84
Chapter 4 Book Tracker Basic App Building

Figure 4-16. Simulator Screen

Congratulations, you have successfully launched the first screen of


your Book Tracker App.

Summary
In this chapter, we created the Tab View controller of our Basic version
of the Book Tracker App. The Tab view controller will have three tabs:
displaying Books, searching existing books, and an option to add a Book to
the repository.
In the next chapter, we will learn about Adding a Book to our Book
Tracker App.

85
CHAPTER 5

Adding Book Screen


In this Chapter, we will build the Add Book Screen. Add Book will allow the
user to add the following attributes of a book.

• Book Name

• Author Name

• Book Description

• Genre Name (User will be able to select from a list


of Genres)

• Status (Read, Reading, to be read)

While adding a book, the following functions will be tested:

• Existing book name with the same author’s name


cannot be created again.
• For creation of a book, at least the Book name and
Author Name will be required.

• By Default, the status will be To Be Read.

• Genre Name will be a pre-populated list. In this book,


we will store in a String Array. User can also create an
administration screen to update the Genre and persist
in a database

© Shantanu Baruah and Shaurya Baruah 2023 87


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_5
Chapter 5 Adding Book Screen

We will learn the following concepts in this section:

• Designing Screen using swift code

• Swift Array handling, for Genre

• Table View core functionality

• Ability to create a dynamic drop-down view using Swift


Table View Concepts

• Custom error handling concepts

• Persist data in iCloud

Designing the Add Screen


Swift provides a few different ways to design screens. In the previous
section, we use the storyboard to design the Tab View navigation.
Storyboard provides a UI-based interface and provides a graphical
interface to drag objects, arrange, and align them and set properties and
connectors. While this method is more user friendly, often, for creating
complex screens, particularly when you have overlapping objects, it
becomes quite a cumbersome process.
Another way to define a screen is to create the objects, layouts, and
its associated properties programmatically. Going forward, we will do all
screen design using swift program.

88
Chapter 5 Adding Book Screen

Create the View Controller File for Add Book:

1. The code of every UI Screen must be linked to the UI


Cocoa Touch Swift file. Go ahead and create a new
file from the file menu, as shown in Figure ­5-­1.

Figure 5-1. Create New View Controller

89
Chapter 5 Adding Book Screen

2. Select the Cocoa Touch class, as shown in


Figure ­5-­2.

Figure 5-2. Project template

90
Chapter 5 Adding Book Screen

3. Give a name to the File. Though you can give any


name, following a nomenclature is a good Practice –
I have named it AddBookViewController. Notice
the subclass is UIViewController and language is
Swift, as shown in Figure 5-3.

Figure 5-3. Create New View Controller – New File Options

91
Chapter 5 Adding Book Screen

4. Once done, save the file in the project folder, as


shown in Figure 5-4.

Figure 5-4. Create New View Controller - File Location

 ssigning the Add View Controller File


A
in Main.storyboard
The next step is to link the newly created Add View controller file to the
Add View Controller screen in Main.storyboard

1. Click on Main Storyboard in the project navigator.

2. Select the Add Book View Controller. You can do


that by clicking on the view controller icon in the
storyboard, which is the first icon on the screen as
shown in Figure 5-5.

92
Chapter 5 Adding Book Screen

Figure 5-5. Linking Add View Controller File to the View


Controller Screen

3. In the Identity Inspector screen, enter the File


name (in our case, it will be AddViewController)
of the view controller. This way we have associated
the View Controller file to the Add View Controller
Screen. The code we write on this file will be for
this screen.

Running the Code


We will now Execute our shell program on a simulator.

1. Select the make of the phone from the set of the


active schemes in the simulator selection screen, as
shown in Figure 5-6.

93
Chapter 5 Adding Book Screen

Figure 5-6. Simulator Selection

2. When the list is dropped down, it will show the


following list of options. If your choice of simulator
is not present, you can Add additional simulators by
clicking on the option shown in Figure 5-7.

Figure 5-7. Simulator Options

94
Chapter 5 Adding Book Screen

3. Run the program by clicking on run button on the


menu bar or by selecting the menu option Run
under the product menu as shown in Figure 5-8.

Figure 5-8. Run Options

95
Chapter 5 Adding Book Screen

4. The simulator will pop up as a separate screen, and


if you have followed the preceding steps, it should
show the screen as shown in Figure 5-9.

Figure 5-9. Simulator Screen

96
Chapter 5 Adding Book Screen

Defining the UI Objects for the Add Screen


The Add Screen will have the following Objects:

1. Book Name – This will be an input text field for user


to add the book name.

2. Author Name – This will be an input text field for


user to add the author’s name.

3. Genre – This will be an input text field for user to


select a genre from a drop-down list.

4. Status – This will be an input text field for the user


to select the status from a drop-down list. Possible
statuses are “Currently Reading,” “To Be Read,”
and “Read.”

Code to Create UI Object

Table 5-1. Life Cycle Methods


Life Cycle Methods
Every View Controller has a collection of lifecycle methods, which are executed
based on certain events. The following code uses the default viewDidLoad() method,
which is called when the view is loaded. To learn more about Lifecycle methods,
please visit section Life Cycle Methods

Open the AddViewController File by clicking on the file name in the


Project navigator. The file content should look like the code shown in
Table 5-2.

97
Chapter 5 Adding Book Screen

Table 5-2. Lifecycle method Code


Code Explanation

// Please note there will be some


// AddBookViewController.swift commented section in the class, added
// MyLibrary during the creation of the file. For
// simplicity reasons, I have deleted it.
// Created by Shantanu Baruah
The class AddBookViewController
on 2/6/21.
extends the system UIViewController
//
class.
import UIKit
viewDidLoad() is the first method
class AddBookViewController: that will be called when the screen is
UIViewController { displayed.

override func viewDidLoad() {


super.viewDidLoad()
}
}

Table 5-3. UI Navigation Bar


UINavigationBar
UIImage and UIImageView
The following code defines the Navigation Bar UI Object. In our App, we are using
UI Navigation Bar Object to display the screen title and two UI Bar buttons, one for
saving the book record and the other to reset the fields. Every UI Object has a view
which can embed new UI Objects. In our following code, we are embedding an UI
Image object into each of the bar button, so that appears visibly appealing.

98
Chapter 5 Adding Book Screen

Add the code shown in Table 5-4 to the class, so the visibility is across the
class. These are UI objects that we will define programmatically.

Table 5-4. Class Level UI Objects


private let addNavigationBar:UINavigationBar = {
let navigationBar = UINavigationBar()
n avigationBar.translatesAutoresizingMaskIntoConstraints = false
n avigationBar.backgroundColor = UIColor.init(red: 171/255,
green: 189/255, blue: 217/255, alpha: 1)
let navItem = UINavigationItem(title: "Add a Book")
l et resetSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 15, weight: .black)
l et resetImage = UIImage(systemName: "arrowshape.
turn.up.left.fill", withConfiguration:
resetSymbolConfiguration)
l et editItem = UIBarButtonItem(image: resetImage,
landscapeImagePhone: resetImage, style: .plain, target:
nil, action: #selector(resetButtonAction))
l et saveSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 20, weight: .black)
l et saveImage = UIImage(systemName: "s.circle.fill",
withConfiguration: saveSymbolConfiguration)
l et saveItem = UIBarButtonItem(image: saveImage,
landscapeImagePhone: saveImage, style: .plain, target:
nil, action: #selector(saveButtonAction))
navItem.rightBarButtonItem = saveItem
navItem.leftBarButtonItem = editItem
navigationBar.setItems([navItem], animated: false)
return navigationBar
}()
(continued)

99
Chapter 5 Adding Book Screen

Table 5-4. (continued)


Navigation Bar helps to navigate between screens and the bar button can also help
take certain actions. In our Add Book Screen, we will have a navigation Bar with two
buttons, one to reset the field values and other is to save the entered book to our
iCloud repository.

let navigationBar = UINavigationBar()

This will define the UI Navigation Bar object and assign it to a variable
navigationBar.

navigationBar.translatesAutoresizingMaskIntoConstraints =
false

We will always set the translatesAutoresizingMaskIntoConstraints of all UI


Objects to false. This is required so that we can set the size and required
constraints programmatically.

navigationBar.backgroundColor = UIColor.init(red: 171/255,


green: 189/255, blue: 217/255, alpha: 1)

This will set the background color of the bar. Please refer to the UI Color
reference section to learn how to set color.

let navItem = UINavigationItem(title: "Add a Book")

We are creating a UINavigationItem Object which can hold a left and right action
button. The constructor takes the title to be displayed on the Navigation Bar.
The next section will create two navigation items and set the items on the
Navigation Bar.

let resetSymbolConfiguration = UIImage.


SymbolConfiguration(pointSize: 15, weight: .black)
(continued)

100
Chapter 5 Adding Book Screen

Table 5-4. (continued)


UIImage symbol configuration is set with image configuration with size 15 and
color black.

let resetImage = UIImage(systemName: "arrowshape.turn.up.left.


fill", withConfiguration: resetSymbolConfiguration)

We are defining an UI Image for the Reset button. iOS provides a list of system-
defined icons. We are using the icon arrowshape.turn.up.left.fill and applying the
symbol configurations for size and color.

let resetItem = UIBarButtonItem(image: resetImage,


landscapeImagePhone: resetImage, style: .plain, target: nil,
action: #selector(resetButtonAction))

We are now assigning the image we created on a UI Bar Button. If a user clicks
on it, it will call the function resetButtonAction ( The system event is Touch Up
Inside). The following three lines are now repeated for the save button.

l et saveSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 20, weight: .black)
l et saveImage = UIImage(systemName: "s.circle.fill",
withConfiguration: saveSymbolConfiguration)
l et saveItem = UIBarButtonItem(image: saveImage,
landscapeImagePhone: saveImage, style: .plain, target:
nil, action: #selector(SaveButtonAction))
navItem.rightBarButtonItem = saveItem

The saveItem we defined before is assigned to the right Bar Button Item of the
navigation Item.

navItem.leftBarButtonItem = resetItem
(continued)

101
Chapter 5 Adding Book Screen

Table 5-4. (continued)


the resetItem we defined before is assigned to the left Bar Button Item of the
navigation Item
navigationBar.setItems([navItem], animated: false)
As the final setup, we will assign the navItem to the Navigation Bar.
return navigationBar
The object is returned to be displayed on the screen.

The reset button and Save button functions defined as part of the
navigation bar item are described in Table 5-5. All user-defined functions
precede with @objc followed by the system-defined func for functions.

Table 5-5. UI Navigation Bar Button Method Definition


@objc func resetButtonAction(sender: UIButton){
}
@objc func saveButtonAction(sender: UIButton){
}
saveButtonAction function is called when the user clicks the Save Button on the Tab
bar. Similarly, resetButtonAction function is called when the user clicks the Reset
Button on the Tab bar. Please note, the functions are currently a shell, the code for
the functions we will discuss in the subsequent sections.

Table 5-6. UI Text Field


UITextField
Beautifying Strings using NSAttributedString
UI Text Field helps in getting data Input from the user. NSAttributedString can be
used to beautify Display Texts on Screen. Attributed Text can be applied to any UI
Object that can display text (UILabel, UITextfield, UIButton, etc.).

102
Chapter 5 Adding Book Screen

Next, we are going to define the Text Field to allow users to enter book-­
related information. The code is defined in Table 5-7.

Table 5-7. UI Text Field Definition and Methods


Code to Create Text Field

//MARK: - Define UI Screen Objects


// is for commenting. Anything we write after // will not be read by the compiler
Mark: are good ways to demark code into sections and provide a textual reference
to what the section is about. In our case, this section is about defining screen
Objects.
private let bookTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.attributedPlaceholder =
NSAttributedString(string: "Enter The Book Name",
attributes: [NSAttributedString.Key.foregroundColor:
UIColor.white])
textField.textColor = .white
return textField
}()
(continued)

103
Chapter 5 Adding Book Screen

Table 5-7. (continued)

1. private let bookTextField:UITextField = {

searchTextField is the variable name of type UITextField. We defined this


variable as let because it won’t be replaced with a new value in the subsequent
code. The variable is also private because it will be only referred to in the Add
Book View Controller file only. Throughout the Add Book View controller file,
we will refer to the book Text Field as bookTextField. Notice the code has ={
the open curly brace is to set properties for the text field as described in the
following:

2. let textField = UITextField()

we are defining a textfield variable. All the properties will be set to the textField
variable, and toward the end of the definition, we will return the bookTextField
variable for display on the view controller.

3. t extField.translatesAutoresizingMaskIntoConstraints =
false

We will always set the translatesAutoresizingMaskIntoConstraints of all UI Object


to false. This is required so that we can set the size and required constraints
programmatically.
4. textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect

this is to set the background color of the Text field to Gray and border style to
rounded Rectangle

5. 
textField.attributedPlaceholder =
NSAttributedString(string: "Enter Book Name", attributes:
[NSAttributedString.Key.foregroundColor: UIColor.white])
(continued)

104
Chapter 5 Adding Book Screen

Table 5-7. (continued)

Attribute Placeholder property provides a prompt text for users to be aware of


what they need to fill. NSAttributedString constructor helps to design the text
appearance (Size, color, font, style). In this example, we give the prompt text to
enter a book name and give the foreground a white color.

6. textField.textColor = .white

the color of the text is set to white as the background is gray

7. return textField
}()

The textField is returned, the curly bracket is closed, and function is completed [()]

private let authorTextField:UITextField = {


let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.attributedPlaceholder = NSAttributedString
(string: "Enter Author Name", attributes:
[NSAttributedString.Key.foregroundColor: UIColor.white])
textField.textColor = .white
return textField
}()
(continued)

105
Chapter 5 Adding Book Screen

Table 5-7. (continued)

private let genreTextField:UITextField = {


let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.attributedPlaceholder = NSAttributedString
(string: "Enter Genre", attributes: [NSAttributed
String.Key.foregroundColor: UIColor.white])
textField.textColor = .white
return textField
}()

private let statusTextField:UITextField = {


let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.attributedPlaceholder = NSAttributedString
(string: "Enter Ststus (Read/Reading/Currently Read",
attributes: [NSAttributedString.Key.foregroundColor:
UIColor.white])
textField.textColor = .white
return textField
}()

This section is the same as the previous, where we have defined the author, genre,
and status text fields.

106
Chapter 5 Adding Book Screen

To draw the UI Objects, we need to set the object Anchors. Since this
will be done multiple times, we will define it once in a custom class called
CommonFunctions.
As shown in Figure 5-10, create a Cocoa Touch Class called
CommonFunctions of subclass type NSObject.

Figure 5-10. Create Common Function Class

We will now add a function which can help anchor the UI Objects on
the screen. The function is public and is at class level. The details are in
Table 5-8.

107
Chapter 5 Adding Book Screen

Table 5-8. UI Object Alignment on Screen Function


func setFieldLayout(mainField:Any, constraintField:Any,
topAnchor: CGFloat, leftAnchor: CGFloat, rightAnchor:
CGFloat,heightAnchor: CGFloat){
(mainField as AnyObject).topAnchor.constraint(equalTo:
(constraintField as AnyObject).topAnchor, constant:
topAnchor).isActive = true
(mainField as AnyObject).leftAnchor.constraint
(equalTo:(constraintField as AnyObject).leftAnchor,
constant:leftAnchor).isActive = true
(mainField as AnyObject).rightAnchor.constraint
(equalTo:(constraintField as AnyObject).
rightAnchor, constant:rightAnchor).isActive = true
(mainField as AnyObject).heightAnchor.constraint
(equalToConstant:heightAnchor).isActive = true
}
(continued)

108
Chapter 5 Adding Book Screen

Table 5-8. (continued)


The function setFieldLayout takes in six variables as described in the following:

• mainField – Object that needs to be positioned on the screen

constraintField – Object which will be used as a reference object for


• 
anchoring
• topAnchor – Space from top of the screen to the Object

• leftAnchor – space from the left of the screen to the Object

• rightAnchor – space from the right of the screen to the Object

• heightAnchor – height of the object

Notice that input variables mainField and ConstraintField is of type Any. This is
because we can send any UI Object to the function which can then be typecast to
the respective UI object types.
(continued)

109
Chapter 5 Adding Book Screen

Table 5-8. (continued)


(mainField as AnyObject).topAnchor.constraint(equalTo:(constraintField as
AnyObject).topAnchor, constant:topAnchor).isActive = true
The passed variable mainField is typecast to AnyObject (for example an UI Text
Field or UI Table View)
topAnchor is a property of UI Object. The topAnchor variable has a constraint
function, which takes two variables – the Top Anchor object it will be constrained to,
and the Top Anchor value. We will make it active by setting the isActive property of
the constraint object to true. (If it is the first object, it will be constrained to the view,
or it could be constrained in relation to the object hierarchy. For instance, if we have
the Author Name as main Field then the constraint field will be Book Name, as the
Author Name input text field comes after the book name text field).
We will repeat this for left, right, and height Anchor.
Notice, for height Anchor, we don’t need to put a constraint, as the height of the
object is not dependent on any other object in the layout.

The code defined in Table 5-9 is for defining variables for


AddBookVIewController class. The variables defined here will be visible
across the class functions.

Table 5-9. Class Level Variable for Accessing Functions Defined in


CommonFunctions
//MARK: - Class Variables
let commonFunctions = CommonFunctions()
To access the functions and variables defined in CommonFunctions class, we
are defining a class level variable called commonFunctions(Notice Swift is case
sensitive). commonFunctions will have the two functions, one for displaying the
object on the screen and the second function to show a spinner when we call iCloud
APIs (iCloud APIs are asynchronous and we need to wait for the call back function
from iCloud to know the API is completed).

110
Chapter 5 Adding Book Screen

We are now going to write a custom function which will draw the
screen for us. We will name the function as drawInputScreen(). This will
be a class level function and will be called when the class will load first
(ViewWillAppear – Please refer to Life Cycle Methods to understand the
lifecycle function hierarchy). The code details are in Table 5-10.

Table 5-10. UI Text Field Definition and Methods


func drawInputScreen(){
view.addSubview(addNavigationBar)
commonFunctions.setFieldLayout(mainField: addNavigationBar,

constraintField: view!, topAnchor: 60, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 40)
view.addSubview(bookTextField)
commonFunctions.setFieldLayout(mainField: bookTextField,

constraintField: addNavigationBar, topAnchor: 60,
leftAnchor: 5, rightAnchor: -5, heightAnchor: 40)
view.addSubview(authorTextField)
commonFunctions.setFieldLayout(mainField: authorTextField,

constraintField: bookTextField, topAnchor: 60,
leftAnchor: 0, rightAnchor: 0, heightAnchor: 40)
view.addSubview(genreTextField)
commonFunctions.setFieldLayout(mainField:
genreTextField, constraintField: authorTextField,
topAnchor: 60, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 40)
view.addSubview(statusTextField)
commonFunctions.setFieldLayout(mainField:
statusTextField, constraintField: genreTextField,
topAnchor: 60, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 40)
}
(continued)

111
Chapter 5 Adding Book Screen

Table 5-10. (continued)

Every ViewController has a default UI view on which the UI Objects are layered.

view.addSubview(addNavigationBar)

To add an UI Object to another object, there is a built-in function called


addSubview. The functions take the object to be added as an input parameter.
In the preceding line of code, the navigation bar is added to the view.

commonFunctions.setFieldLayout(mainField: addNavigationBar,
constraintField: view!, topAnchor: 60, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 40)

Notice we are calling the setFieldLayout from the CommonFunctions class


using the variable commonFunctions. In the preceding statement, we are
defining the coordinates of the navigation bar in relation to the default view. The
view will be 60 pixels from the top, covering the entire screen width (notice left
and right anchor as 0) and the height of the navigation bar at 40 pixels.

view.addSubview(bookTextField)

commonFunctions.setFieldLayout(mainField: bookTextField,
constraintField: addNavigationBar, topAnchor: 60, leftAnchor:
5, rightAnchor: -5, heightAnchor: 40)

The preceding two statements add the book Input Text Field below the
navigation bar. Notice the constraint field this time is the navigation bar, and it is
separated by 60 pixels. We are leaving 5 pixels from the left and the right.

The Author, Genre, and Text Field are added to the screen in a similar way.
Please note how the constraint field is changing for each one of them. Also
note, the right and left anchor are set to zero because we want all fields to be
of the same size as Book Text field, as these fields are taking Book Text Field as
constraint.

112
Chapter 5 Adding Book Screen

The code defined in Table 5-11 is the lifecycle method, which will
execute first as the Add Book screen opens.

Table 5-11. Lifecycle – View Did Load


override func viewDidLoad() {
super.viewDidLoad()
drawInputScreen()
}
viewDidLoad method is the first method called when the view is loaded. We are
calling the drawInputScreen method from here, as we want to display the screen
elements first.

113
Chapter 5 Adding Book Screen

Running the Program


If all steps are followed properly, the simulator would look like Figure 5-11.

Figure 5-11. Simulator – Add a Book

114
Chapter 5 Adding Book Screen

Saving the Book Record in iCloud


Now that we have designed the Add book screen, we will save the record to
the Book entity defined in the iCloud database Genie.

Data Validation Is an Important Step


Before saving the record, we need to perform some basic validation. We
want to make sure there is some value for all the text fields. The validation
will be triggered when the user attempts to save the book record. The code
in Table 5-12 provides the details.(Recall the function saveActionButton
attached to the Save Button on the tab bar.) We will perform the
following action:

1. Check if the text fields are empty.

2. If so, put a placeholder text indicating field cannot


be blank.

3. To add emphasis on the message, we will make the


border of the text field red where value is blank.

4. And put the focus back on the first empty text field,
forcing the user to enter value.

115
Chapter 5 Adding Book Screen

Table 5-12. Saving Book Record


1. @objc func saveButtonAction(sender: UIButton){

2. var canSave:Bool = true

3. if(bookTextField.text == ""){
bookTextField.placeholder = "Book Name cannot be blank"
bookTextField.layer.borderWidth = 2
bookTextField.layer.cornerRadius = 5
bookTextField.layer.borderColor = UIColor.red.cgColor
canSave = false
4. }else{
bookTextField.layer.borderWidth = 0

5. }

6. if(authorTextField.text == "")

7. {
authorTextField.placeholder = "Author Name cannot be
blank"
authorTextField.layer.borderWidth = 2
authorTextField.layer.cornerRadius = 5
authorTextField.layer.borderColor = UIColor.red.cgColor
canSave = false

8. }else{
authorTextField.layer.borderWidth = 0

9. }
(continued)

116
Chapter 5 Adding Book Screen

Table 5-12. (continued)


10. if(genreTextField.text == ""){
genreTextField.placeholder = "Genre cannot be blank"
genreTextField.layer.borderWidth = 2
genreTextField.layer.cornerRadius = 5
genreTextField.layer.borderColor = UIColor.red.cgColor
canSave = false

11. }else{
genreTextField.layer.borderWidth = 0

12. }

13. if(statusTextField.text == ""){


statusTextField.placeholder = "Status cannot be blank"
statusTextField.layer.borderWidth = 2
statusTextField.layer.cornerRadius = 5
statusTextField.layer.borderColor = UIColor.red.cgColor
canSave = false

14. }else{
statusTextField.layer.borderWidth = 0

15. }

16. if(canSave){
saveBook()

17. }

18. }
(continued)

117
Chapter 5 Adding Book Screen

Table 5-12. (continued)

1. @objc func saveButtonAction(sender: UIButton){

This is the function which gets called when the user clicks on the save button.
2. var canSave:Bool = true

This is a function level variable which is set to false if any of the field is left
blank

3. if(bookTextField.text == ""){

This statement compares if the book text field is blank. If it is blank, the
following five statements are executed

bookTextField.placeholder = "Book Name cannot be blank"

Set the placeholder value of the book text to a custom text

bookTextField.layer.borderWidth = 2

We are setting the Text field border to size 2 to make it promptly visible

bookTextField.layer.cornerRadius = 5

The text field corner radius will give the rectangular text box a nice, rounded
curve

bookTextField.layer.borderColor = UIColor.red.cgColor

We are setting the border color to Red to make it standout

canSave = false

Setting the canSave Boolean variable to false, flagging we cannot save the
record yet
(continued)

118
Chapter 5 Adding Book Screen

Table 5-12. (continued)

4. }else{

If enclosure, and the else part of the if (when the text field is not blank)
bookTextField.layer.borderWidth = 0

setting the border width to zero so that it returns to the original status

5. }

Else statement enclosure.


line 6 to 14, it is the repeat validation for other fields. Notice that each text
field validation is a separate block of if statement instead of an else if. This is
because we want validation to be checked for all fields at once and let the user
know of all possible fields that need value. If we use else if, then the checking
will be progressive, or in other words, even if all fields are blank, it will show
Book does not blank first and when you save again with Book name entered, it
will show author did not find the next time around.

6. if(authorTextField.text == ""){
authorTextField.placeholder = "Author Name cannot be
blank"
authorTextField.layer.borderWidth = 2
authorTextField.layer.cornerRadius = 5
authorTextField.layer.borderColor = UIColor.red.cgColor
canSave = false

7. }else{
authorTextField.layer.borderWidth = 0
8. }
(continued)

119
Chapter 5 Adding Book Screen

Table 5-12. (continued)

9. if(genreTextField.text == ""){
genreTextField.placeholder = "Genre cannot be blank"
genreTextField.layer.borderWidth = 2
genreTextField.layer.cornerRadius = 5
genreTextField.layer.borderColor = UIColor.red.cgColor
canSave = false

10. }else{
genreTextField.layer.borderWidth = 0

11. }

12. if(statusTextField.text == ""){


statusTextField.placeholder = "Status cannot be blank"
statusTextField.layer.borderWidth = 2
statusTextField.layer.cornerRadius = 5
statusTextField.layer.borderColor = UIColor.red.cgColor
canSave = false

13. }else{
statusTextField.layer.borderWidth = 0

14. }

15. if(canSave){

If canSave is set to true, it means all fields have the needed values and we
can save the book

16. saveBook()
Functions to save the book
17. }

      }

120
Chapter 5 Adding Book Screen

Note There are other validations we need to do, for example, if the


book already exists in the database, we do not want to save it again.
We will do this validation as part of saving.

Run the program. You will see screens like how it is shown in
Figure 5-12.

:KHQ DOO ILHOGVDUH :KHQ %RRN QDPH :KHQ $XWKRU LV :KHQ *HQUH LV :KHQDOO ILHOGV
EODQN WKH UHG LVHQWHUHG DOO RWKHU HQWHUHGDOO RWKHU HQWHUHGDOO RWKHU KDYH YDOXH
ERUGHU LV RQ DOO ILHOGV KDYH UHG ILHOGV KDYH UHG ILHOGV KDYH UHG
ILHOGV ZLWKWKH ERUGHUV ZLWK ERUGHUV ZLWK ERUGHUV ZLWK
ZDUQLQJ ZDUQLQJ ZDUQLQJ ZDUQLQJ
SODFHKROGHU WH[W SODFHKROGHU WH[W SODFHKROGHU WH[W SODFHKROGHU WH[W

Figure 5-12. Simulator – Add a Book Validation – Multiple Option

121
Chapter 5 Adding Book Screen

In the current validation, the red border doesn’t go away even after
providing a value until the user clicks on the save button. Aesthetically, it
doesn’t appear right. The code in Table 5-13 helps to solve that problem.
The steps are as follows:

• Text Fields have a built-in action called When Begin


Editing. This is called when the user starts typing.

• We will define this function for all the four-text fields


and, in the body of the function, we will make the
border width of the text field to zero.

Table 5-13. Text Field Validation User Interaction


textField.addTarget(self, action: #selector
(textFieldEditingDidBegin), for: .editingDidBegin)
Add the preceding line to all the four Text field Definitions. Notice that the same
function will be called every time user starts.

• .editingDidBegin is a built-in function of the text field.

• addTarget allows to add custom method for certain UI Actions.

• 
#selector allows defining a custom function for the action. In our case, it
is textFieldEditingDidBegin. We need to define this function to achieve the
desired behavior.

//MARK: - Text Field Functions


@objc func textFieldEditingDidBegin(sender: UITextField){
sender.layer.borderWidth = 0
}

This is our custom method and is called when the user starts typing on any of the
text fields. The sender has the reference to the Text Field, and we can use it to set
the border width to zero.

122
Chapter 5 Adding Book Screen

Create a Database Function File


Create a Cocoa Touch file called DatabaseFunctions of type NSObject.
This file will house all the functions required to interact with the Genie
database.

Preparation Before Saving the Book


Data Class
We will define a Book class which will mimic the Book Class we
created in the iCloud. This class we will define in the DatabaseFunctions
file as a class level object. Code is defined in Table 5-14.

Table 5-14. Book Class Definition


Class Book{
var bookname:String!
Var authorname:String!
Var genre:String!
Var status:String!
}

Referencing Database Class


In the AddBookViewController class, we will create an object of
the class DatabaseFunctions so that we can reference the objects and
functions to save the book. We will also create an object called book of
class Book (Book is a custom class defined in CommonVariables Class.
We will learn it later in the book). This is where we will store the book
details, as shown in Table 5-15.

Table 5-15. Class Level Variables


let databaseFunctions = DatabaseFunctions()
let book = DatabaseFunctions.Book()

123
Chapter 5 Adding Book Screen

Define Group
In the DatabaseFunctions class define a dispatch group and a
Queue. We will need this because all calls to the iCloud CRUD functions
(create, query, update, and delete) are asynchronous. So, when we call
these functions, we would be notified when the job is done (as iCloud is
a repository located outside of your App). The Dispatch group helps us
to handle the notification and the queue will be defined to know where it
should be notified when the work is done. Define this at the class level. So,
all functions can use it. See the code in Table 5-16 for details.

Table 5-16. Asynchronous Queue Definition


let group = DispatchGroup()
let syncQueue = 
DispatchQueue(label: "com.domain.app.sections")
Dispatch Group and Dispatch Queue are defined. Please note, label is a custom label
for unique identification.

Function to Save Book Record


Cloudkit Library as shown in Table 5-17

Table 5-17. Import Cloudkit


import CloudKit
Import Cloudkit library to access all iCloud built-in methods in the
DataBaseFunctions class.

124
Chapter 5 Adding Book Screen

Save the Book to iCloud – code in Table 5-18.

Table 5-18. Code to Save Book Record


func saveBook(book:Book){
let ckRecordZoneID = CKRecordZone(zoneName:
"_defaultZone")
let privateDatabase = CKContainer.default().
privateCloudDatabase
let ckRecordID = CKRecord.ID(zoneID: ckRecordZoneID.
zoneID)
let aRecord = CKRecord(recordType: "Book", recordID:
ckRecordID)
aRecord["bookname"] = book.bookname
aRecord["authorname"] = book.authorname
aRecord["genre"] = book.genre
aRecord["status"] = book.status
privateDatabase.save(aRecord, completionHandler: {
(record, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print(error)
}
else{
self.syncQueue.async {
self.group.leave()
}
}
}
})
}
(continued)

125
Chapter 5 Adding Book Screen

Table 5-18. (continued)

1. func saveBook(book:Book){

This is a custom function to save the Book data entered by the user. It takes an
object of type Book class.
Row 2 to 5 is getting access to the custom Shared Zone of the Cloud Database
for the user (Private Database). Please note, a custom zone is required for record
sharing with other users, which we will learn later. By default, iCloud creates a
default zone, which cannot be used for sharing records.

2. 
let ckRecordZoneID = CKRecordZone(zoneName:
"_SharedZone")

We are defining the zone ID where we are going to store our book record.
SharedZone is a custom name.

3. l et privateDatabase =
CKContainer.default().privateCloudDatabase

Reference to the user default private database is stored in the privateDatabase


variable. Please note every user will have its own database which no one else
can see. iCloud knows about it from the user Apple ID.

4. l et ckRecordID = CKRecord.ID(zoneID:
ckRecordZoneID.zoneID)

We are now defining a Record ID to hold our book pointing to the Shared Zone

5. 
let aRecord = CKRecord(recordType: "Book",
recordID: ckRecordID)

The record ID is now pointing to Book Record Type, which, if you recall, we
created during iCloud setup. We will use this record to store our book details.
(continued)

126
Chapter 5 Adding Book Screen

Table 5-18. (continued)


6. 
aRecord["bookname"] = book.bookname

The function is called with a book object. We are now assigning the book object
book name value to the record with the field “bookname”. Please note, the name
of the field should exactly match to the field name we provided while creating the
book. Steps 7, 8, and 9 is a repeat of Step 6 for other book fields.

7. aRecord["authorname"] = book.authorname

8. aRecord["genre"] = book.genre

9. aRecord["status"] = book.status

10. 
privateDatabase.save(aRecord, completionHandler: {
(record, error) -> Void in

recall we have the variable privateDatabase pointing to the User Private Database
in iCloud. We are calling the built-in save method to save our record. Save
takes the CKRecord Type (which in our case is Book type) parameter and it has
a completion handler. Please note, all iCloud functions are asynchronous. So,
when the database is done with the work, it returns either a record it saved (if
successful) or an error message. The parameter record and error are to capture
the same.

11. DispatchQueue.main.async {

On the main thread of the Dispatch Queue, the async routine will invoke when
the work is done.

12. if let error = error {

we are comparing if there is any error.


(continued)

127
Chapter 5 Adding Book Screen

Table 5-18. (continued)

13. print(error)

If so, we will print the error on the console

14. }

15. else{

16. self.syncQueue.async {

or else, we will notify the waiting process (from where our custom saveBook()
will be called)

17. self.group.leave()

group leave will notify the waiting function (from where our custom saveBook()
will be called)

18. }

19. }

20. }

21. })

22. }

Setting Value Before Calling saveBook


In the view controller, AddBookViewController method
saveButtonAction (when the save button is clicked, this function is
called), we will add the text as shown in Table 5-19 when the canSave (it is
a variable of Boolean type) is set to true (when all text fields are not blank).

128
Chapter 5 Adding Book Screen

Table 5-19. Setting Book Values


1. if(canSave){
2. book.bookname = bookTextField.text!
3. book.authorname = authorTextField.text!
4. book.genre = genreTextField.text!
5. book.status = statusTextField.text!
6. saveBook()
7. }
1. if(canSave){

When CanSave is true, all text fields have some value

2. book.bookname = bookTextField.text!

setting the book object book name property to the book Text Field value

3. book.authorname = authorTextField.text!

setting the book object Author Name property to the Author Text Field value

4. book.genre = genreTextField.text!

setting the book object Genre property to the Genre Text Field value

5. book.status = statusTextField.text!

setting the book object Status property to the Status Text Field value

6. saveBook()

calling the saveBook Function

7. }

Call the Save Book Method


We will now call the function saveBook in the DatabaseFunctions.
Please note, saveBook must be called in an asynchronous fashion. This
function is defined in the AddBookViewController file. The code details
are in Table 5-20.
129
Chapter 5 Adding Book Screen

Table 5-20. Calling the Save Book Functions


1. fileprivate func saveBook(){
2. databaseFunctions.group.enter()
3. databaseFunctions.saveBook(book:book)
4. databaseFunctions.group.notify(queue: .main) {
5. print("Record Saved")
6. }
7. }
1. fileprivate func saveBook(){

fileprivate before a func makes it only accessible in the defined file. Please note,
this is the local saveBook() function to the file AddBookViewController.swift

2. databaseFunctions.group.enter()

we are accessing the group variable from the DatabaseFunctions file using the
reference variable databaseFunctions. enter() allows us to enter the wait loop. It
will wait until it is notified.

3. databaseFunctions.saveBook(book:book)

After entering the wait, we are now calling the DataBaseFunctions saveBook
method. Notice it takes Book as a variable. Also note, we have stored the class
instance book with all required values when the save button was clicked.

4. databaseFunctions.group.notify(queue: .main) {

When iCloud returns to the main queue, it will notify the group object. At this
time, we can do any post-saving activities.

5. print("Record Saved")

For now, we are simply printing Record Saved on the console.


6. }

7. }

130
Chapter 5 Adding Book Screen

Post Save
UIAlertController
Alerts are a quick way to display contextual information based on certain actions
that a user performs. Alert can also show action buttons and, based on what user
chooses, a certain function can be executed.

When you run the program now and save a book, the screen shows all
the last entered book data, except printing “Record Saved” on the console.
After saving, we would do the following:

• Show an Alert informing the user the record is saved.

• Clean the Fields, allowing user to add new


Book Record.

• Bring the focus to the first field – Book Name.

We are adding code after the book is saved. The highlighted portion in
Table 5-21 is what we replaced from the book save function.

131
Chapter 5 Adding Book Screen

Table 5-21. Post Save Code


fileprivate func saveBook(){
databaseFunctions.group.enter()
databaseFunctions.saveBook(book:book)
databaseFunctions.group.notify(queue: .main) {
let alertController = UIAlertController(title:
"Save Alert", message: "Book '\(self.book.
bookname!)' is Saved", preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK",
style: UIAlertAction.Style.default) {
UIAlertAction in
self.tabBarController?.selectedIndex = 0
}
// Add the actions
alertController.addAction(okAction)
self.resetField()
// Present the controller
self.present(alertController, animated: true,
completion: nil)
}
}
1. 
let alertController = UIAlertController(title: "Save
Alert", message: "Book '\(self.book.bookname!)' is
Saved", preferredStyle: .alert)

We are creating an Alert to display the message “Book is Saved” Message.

2. // Create the actions


(continued)

132
Chapter 5 Adding Book Screen

Table 5-21. (continued)

3. 
let okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {

We need to create an action button on the alert window – this is the OK Button.
We will only have one button for this alert, as this is information-only alert
window.

4. UIAlertAction in

This is inline code. If we need to take any action, we will take it here. For our
window, we are not taking any action, so it is blank.

5. }

6. // Add the actions

7. alertController.addAction(okAction)

Adding the action to the Alert object.

8. // Present the controller

9. 
self.present(alertController, animated: true,
completion: nil)

presenting the alert on the view.

10. self.resetField()

This is a custom function to reset the field value (Defined in the next section)

133
Chapter 5 Adding Book Screen

Run the program. You will see a screen, as shown in Figure 5-13, after
the book is saved.

Figure 5-13. Simulator - Saving Book

Reset Field
When the user clicks the reset button on the navigation bar, all data is
removed, and the text fields show the placeholder texts. Consider this
when you want to clear the fields without manually going to every field to
delete the words. Table 5-22 has the relevant code.

134
Chapter 5 Adding Book Screen

Table 5-22. Reset Book Data


@objc func resetButtonAction(sender: UIButton){
resetField()
}
1. @objc func resetButtonAction(sender: UIButton){

resetButton function is defined for the navigation bar left button. This is the
implementation of the same

2. resetField()

This is a custom function to reset the field value


3. }

func resetField(){
bookTextField.text = ""
authorTextField.text = ""
genreTextField.text = ""
statusTextField.text = ""
bookTextField.becomeFirstResponder()
}
1. func resetField(){
Custom function definition

2. bookTextField.text = ""

Book Name Text Field is set to empty string

3. authorTextField.text = ""

Author Name Text Field is set to empty string

4. genreTextField.text = ""

Genre Text Field is set to empty string


(continued)

135
Chapter 5 Adding Book Screen

Table 5-22. (continued)

5. statusTextField.text = ""

Book Status Text Field is set to empty string

6. bookTextField.becomeFirstResponder()

Making the Book Name Field the first Responder, which will bring the cursor to
the Book Name Text Field and bring up the keyboard.

7. }

Summary
This chapter introduced many core concepts which will be your learning
bedrock for building iOS App. We learned about Tab View Screen
allocation and transition, input validation and user interactions. We also
got introduced to CloudKit for storing our book record Asynchronously to
iCloud repository. After adding the necessary code for Adding a Book to
our Book Tracker repository, we learned how to test our application using
iOS simulator. iOS simulator is a great way to test form response on various
models of iPhone.
Now that we have books in our Book Tracker database, next we will
learn about retrieving them from iCloud and displaying them on our App.

136
CHAPTER 6

Displaying the Book


Records
In this chapter, we will learn about displaying the books we have stored in
our Book Tracker repository. Following are the key functions we need to
perform to achieve the same:

• Create a View Controller for Displaying the Book


records.

• Query the iCloud repository to retrieve all user books.

• Implement Table View to Display the books.

Setting Up Display View Controller


If you recall, we have set up a Tab View Controller with three tabs,
“Display,” “Add Book,” and “Search.” We will use the first Tab to display
our books, and to do that we need to create a UIViewController with the
name DisplayViewController. This is the same way how we created the
AddViewController. For ease of reference, I am re-introducing the steps
as follows:

© Shantanu Baruah and Shaurya Baruah 2023 137


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_6
Chapter 6 Displaying the Book Records

Create the View Controller File for Display Book Screen

1. The code of every UI Screen must be linked to the UI


Cocoa Touch Swift file. Go ahead and create a new
file from the file menu, as shown in Figure 6-1.

Figure 6-1. Create View Controller

138
Chapter 6 Displaying the Book Records

2. Select the Cocoa Touch class, as shown in


Figure 6-2.

Figure 6-2. Project Template

139
Chapter 6 Displaying the Book Records

3. Give a name to the File. Though you can give any


name, following a nomenclature is a good Practice –
I have named it DisplayBookViewController.
Notice that the subclass is UIViewController and
the language is Swift. The details of the screen is
shown in Figure 6-3.

Figure 6-3. View Controller Options

140
Chapter 6 Displaying the Book Records

4. Once done save the file in the project folder, as


shown in Figure 6-4.

Figure 6-4. File Saving Screen

 ssigning the Display View Controller File


A
in Main.storyboard
The next step is to assign the newly created Display View controller file to the
Display View Controller screen in Main.storyboard, as shown in Figure 6-5.

1. Click on Main Storyboard in the project navigator.

2. Select the Display Book View Controller. You can


do that by clicking the view controller icon in the
storyboard, which is the first icon on the screen, as
shown in the following.

141
Chapter 6 Displaying the Book Records

Figure 6-5. Storyboard View Controller Assignment

3. In the Identity Inspector screen, enter the File name


(in our case it will be DisplayViewController) of
the view controller. This way we have associated the
View Controller file to the Display View Controller
Screen. The code we write on this file will be for
this screen.

Query the Book Table


Before we can display the book on the Display View Controller screen, we
need to first retrieve the books we have saved to our iCloud database –
Book. For querying the books, we need to use functions from the CloudKit
framework. In the following code snippet, we are retrieving all saved
books. Later we will also learn to query for a particular book.

142
Chapter 6 Displaying the Book Records

Like how we Saved the Book, all CRUD functions we will write in one
file (DatabaseFunctions) – this increases modularity and confirms the
Object-Oriented Programming.
Write the piece of code as described in Table 6-1 to the
DatabaseFunctions.

Table 6-1. Query Book Code Details


var books = [Book]()
1. var books = [Book]()
define a books variable of type Book which will hold all the books returned by our
book query function (Definition of Book is in DatabaseFunctions Class)

func bookQuery() {
let pred = NSPredicate(value: true)
let query = CKQuery(recordType: "Book," predicate: pred)
let operation = CKQueryOperation(query: query)
CKContainer.default().privateCloudDatabase.add(operation)
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
operation.zoneID = ckRecordZoneID.zoneID
operation.recordFetchedBlock = { record in
let book = Book()
book.bookname = record["bookname"]
book.authorname = record["authorname"]
book.genre = record["genre"]
book.status = record["status"]
self.books.append(book)
}
(continued)

143
Chapter 6 Displaying the Book Records

Table 6-1. (continued)


o peration.queryCompletionBlock = { [unowned self] (cursor,
error) in
DispatchQueue.main.async {
if let error = error {
print(error)
}else {
self.syncQueue.async {
self.group.leave()
}
}
}
}
}
1. // MARK: – Retrieve All Books

Remarks to create a MarkUp block for the Book Query code. This is for better
readability.

2. func bookQuery() {

This is the name of the functions that will hold book query functionalities.
3. let pred = NSPredicate(value: true)

NSPredicate is a functional part of the CloudKit framework, which is used to build


conditional statements. As we are retrieving all books, we are setting the Boolean
value to true, indicating we need all books.

4. let query = CKQuery(recordType: "Book", predicate: pred)


CKQuery is a CloudKit function used to build the query. The constructor takes two
values, the name of the record Type (Table where book is stored hence the name
Book – please refer to schema creation section in iCloud), and the predicate, which
we have set up before to retrieve all records.
(continued)

144
Chapter 6 Displaying the Book Records

Table 6-1. (continued)


5. let operation = CKQueryOperation(query: query)

Now, we are setting the query to the CKQueryOperation function


6. CKContainer.default().privateCloudDatabase.add(operation)

The operation we have set with the query is now added to the user private cloud
database where the user schema exists. Currently, the query is sent asynchronously
to the iCloud for execution. Please note the following statement will only be
executed when the query returns to this thread. This is the reason why when we call
the bookQuery() function we will put it in a wait loop.

7. operation.recordFetchedBlock = { record in

When iCloud returns, it will first land on the record fetched block. Notice the word
record which holds the individual book record (as per the Book table defined in
iCloud Data Dictionary). As we loop through the fetched records, we can store it in
a local Book (Book is a custom class defined in DatabaseFunctions class, which
mimics the Book Table defined in the iCloud database) array for display purposes.

8. let book = Book()

we are creating a local instance of the Book class

9. book.bookname = record["bookname"]

storing the returned bookname to the local book.bookname variable

10. book.authorname = record["authorname"]

storing the returned authorname to the local book.authorname variable

11. book.genre = record["genre"]

storing the returned genre to the local book.genre variable


(continued)

145
Chapter 6 Displaying the Book Records

Table 6-1. (continued)


12. book.status = record["status"]

storing the returned status to the local book.status variable

13. self.books.append(book)

Now that we have the book information in the local variable book, we will add this
to our class level books array. The loop will continue until all books are retrieved and
stored.

14. }

15. 
operation.queryCompletionBlock = { [unowned self] (cursor,
error) in

when the previous fetch is complete, the query completion block is called. Notice
you get the cursor to the record set (we will not use it in our code) and if there are
any errors it will be stored in the error variable.

16. DispatchQueue.main.async {

17. if let error = error {

This statement stores the error returned from the asynchronous iCloud function into
a variable named error.
18. print(error)

If the previous statement satisfies, it will print the error.

19. }else {

20. self.syncQueue.async {
We have defined a custom queue called syncQueue, on which we will wait for the
asynchronous function to return.
(continued)

146
Chapter 6 Displaying the Book Records

Table 6-1. (continued)


21. self.group.leave()

And when it returns, we will leave the thread


22. }

23. }

24. }

25. }

Call the Query Book


The CloudKit function to retrieve the already created book records
need to be called when the Display View Controller opens. If you have
noticed, the Display View controller is our first View. We need to use
one of the Lifecycle methods to call the function. I prefer placing it in
viewDiDAppear, as this will be called whenever we tap on the Display
Tab. Another important point to note is the CloudKit functions are
asynchronous, so, when we call the function, we need to wait for it to
return. We do this using the DispatchGroup functions, which will allow us
to enter the wait and get notified when the function completes. Look at the
code snippet as described in Table 6-2:

147
Chapter 6 Displaying the Book Records

Table 6-2. iCloud Query Book Code Details


import UIKit
class DisplayViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource {
// the two following variables are used to access the common
class functions and variables
let databaseFunctions = DatabaseFunctions()
1. let databaseFunctions = DatabaseFunctions()

Defining a variable databaseFunctions as an instance of DatabaseFunctions class


to access all CloudKit related functions

//MARK: - Book Query


fileprivate func bookQuery(){
databaseFunctions.group.enter()
databaseFunctions.bookQuery()
databaseFunctions.group.notify(queue: .main) {
self.setup()
self.drawBookDisplay()
self.bookTableView.reloadData()
}
}
1. fileprivate func bookQuery(){

defining a custom function bookQuery. fileprivate makes it only accessible within


the class

2. databaseFunctions.group.enter()

We have defined a variable called group in DatabaseFunctions class of type,


DispatchGroup enter will initiate the wait thread
(continued)

148
Chapter 6 Displaying the Book Records

Table 6-2. (continued)


3. databaseFunctions.bookQuery()

Now we will call the bookQuery functions from DatabaseFunction (we wrote in the
previous section), which will query the CloudKit database schema.

4. databaseFunctions.group.notify(queue: .main) {

this is the wait part of the DispatchGroup, when the child thread notifies the main
thread, the code in this block will be executed.

5. self.drawBookDisplay()

This is the function to display the table view, which we will discuss in the next
section.

6. }

7. }

Create a Table View


Table 6-3. UI Table Definition
UITable
Table Views are one of the most important UI Objects in the iOS toolkit. A table
view displays a collection of vertical rows where each row contains a collection of
information that is displayed on the screen. The table view is scrollable, allowing
users to scroll through a long list of content. Table view also allows grouping of
content and shows the content in separate sections. Each displayed row is a table
view cell, which can be customized to display any UI Object.

149
Chapter 6 Displaying the Book Records

Now that we have queried the Cloudkit table, next we need to display the
value on the screen. Swift has a UIObject called Table View which we will
use for this purpose. Table View is one of the most important UI Objects
that we need to learn to create iPhone Apps. The default Weather, Stock
Ticker Apps are customized rendition of Table View. In the Core concepts
section, we will dedicate a section on Table View, its related functions,
and how to customize a table to create aesthetically aligned professional
applications. For now, we will learn the basic table view setup to display all
books on a one column table.
Before we start, let us understand a few important aspects of
table view.

• Table View is a collection of table view cells.

• Each Table View Cell is a view which by default has a UI


label. We can customize the table view cell to have any
UI Object.

• Displaying Cell is a resource-expensive process; hence,


by default, Table View is designed to repurpose cells
when we scroll between values. For example, if we have
a 100-row table and the screen at a time displays only
ten cells, the Table View repurposes these ten cells to
display the next ten values as we scroll down or up. This
is important to note, because if we do not repurpose
the cells, the application will become quite slow.

• Table View has many built-in functions, but


there are two functions which require definition:
numberOfRowsInSection – This lets the Table View
know how many rows exist, and cellForRowAt – This
will reiterate as many times as there are visible cells or
number of rows, whichever is larger.

Let us now implement our Book table view to display the retrieved books.

150
Chapter 6 Displaying the Book Records

Step 1: Define the Table Object


The code in Table 6-4 has the UI Objects we will need to display the
queried books from the iCloud repository.

Table 6-4. Display UI Object Code


private let bookTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()
1. private let bookTableView:UITableView = {

bookTableView is a variable name of type UITableView. We are defining this as a


private variable as the scope is only for this class

2. let tableView = UITableView()

Table View is created and stored in a variable called tableView. We will use
tableView variable to set some initial properties of the table
3. 
tableView.translatesAutoresizingMaskIntoConstraints =
false

translatesAutoresizingMaskIntoConstraints property of UI Object allows us to define


our own constraints when set to false

4. tableView.backgroundColor = .white

Setting the table view background color to white. Note, we can set a custom color
using the UIColor constructor
(continued)

151
Chapter 6 Displaying the Book Records

Table 6-4. (continued)


5. tableView.isScrollEnabled = true

We want the table to scroll to display all books as user scrolls up or down

6. return tableView

we are returning the tableView, so, when we use the bookTableView variable, all the
settings we have set will be available

7. }()

 tep 2: Extending the Table Delegate and Table


S
Data Source
Table delegation and source are defined in Table 6-5.

Table 6-5. Table Delegate and Source Definition


class DisplayViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource {
1. 
class DisplayViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource {

We need to extend Table View Delegate and Data Source. This is required to
enable all Table View functions. Please note, the moment you add the delegates,
you will get an error stating, DisplayViewController does not conform to protocol
“UiTableViewDataSource”. We need to add a minimum of two methods as
explained in steps 5 and 6.

Step 3: Setting the Table Delegate and Table Cell


It is a good practice to create a setup function and call it when the View
Appears. In our current implementation, we need to do some initial setup
of the table, setting up the bookTableView delegate and data source to self.

152
Chapter 6 Displaying the Book Records

Table view displays values in Table View Cell, we need to also define the
TableView Cell (Note, Swift provides a default Table View Cell, which we will
use for now. Later we will learn about creating a custom Table View Cell for
custom table view appearances). Table 6-6 has the relevant code details.

Table 6-6. Table Delegate and Source Settings


func setup(){
bookTableView.delegate = self
bookTableView.dataSource = self
b ookTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "book")
}
1. //MARK: - Setup

2. func setup(){

Defining the custom setup functions

3. bookTableView.delegate = self

setting the bookTableView Delegate to the class so that we can perform all Table
View functions

4. bookTableView.dataSource = self

setting the bookTableView Data source to the class so that we can perform all
Table View functions

5. 
bookTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "book")

Registering the table view to a default Cell Type and giving it an identifier name
“book”. We will use this identifier to display table value in the cell

6. }

153
Chapter 6 Displaying the Book Records

Step 4: Drawing the Table


Table 6-7 has the code for accessing the functions defined in the class
CommonFunctions.

Table 6-7. Class Variable


let commonFunctions = CommonFunctions()
1. let commonFunctions = CommonFunctions()

Defining a variable commonFunctions as an instance of CommonFunctions class


to access all custom common functions

Since we are drawing UI Objects using code, our next step is to draw
the screen with the table. Table 6-8 has the relevant code details for
drawing the Book Display screen.

Table 6-8. Table Delegate and Source Definition


func drawBookDisplay(){
view.addSubview(bookTableView)
c ommonFunctions.setFieldLayout(mainField: bookTableView,
constraintField: view!, topAnchor: 100, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 400)
}
1. //MARK: - Draw Book Display

2. func drawBookDisplay(){

drawBookDisplay is a custom function to display the book Table on the screen

3. view.addSubview(bookTableView)
adding the bookTableView to the main View
(continued)

154
Chapter 6 Displaying the Book Records

Table 6-8. (continued)


4. 
commonFunctions.setFieldLayout(mainField: bookTableView,
constraintField: view!, topAnchor: 100, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 400)
calling the fieldLayout functions in commonFunctions file to set the book table on
screen. Notice we are setting it 100 pixels from the top covering the entire width,
and height of the table set to 400 pixels.

5. }

Step 5: Implementing numberOfRowsInSection


This is a mandatory function for Table View display. This function makes
the table view aware of the number of rows to display. Table 6-9 has the
relevant code details.

Table 6-9. Table Number of Rows in Section Details


func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return self.databaseFunctions.books.count
}
1. f unc tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {

this is built-in function signature which returns an integer value, which refers to the
total number of rows in a table

2. return self.databaseFunctions.books.count

Recall from our Query Book functions, after the query, the result is persisted in the
books variable in the databaseFunctions file.

3. }

155
Chapter 6 Displaying the Book Records

Step 6: Implementing the cellForRowAt


This is also a mandatory function when we implement table view with
data source. This function will iterate for the number of visible rows on the
screen or the total number of rows, whichever is smaller. Please note this
is important, for Table View reuses the cell to save memory, when the user
scrolls down or up. Table 6-10 has the relevant code details.

Table 6-10. Table Cell Display Function Details


func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
l et cell = tableView.dequeueReusableCell(withIdentifier:
"book", for: indexPath)
c ell.textLabel?.text = self.databaseFunctions.
books[indexPath.row].bookname
return cell
}
1. f unc tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {

This is a built-in function signature which returns an UI table View Cell to display
custom value (in our case book name). This will iterate for all visible rows

2. l et cell = tableView.dequeueReusableCell(withIdentifier:
"book", for: indexPath)

we are defining a variable of type cell which uses the dequeueReusableCell


property of tableView. Recall the identifier “book” we have set before in setup. The
identifier name should match. It also takes the current indexPath. IndexPath has
reference to section and row (section we will learn later)
(continued)

156
Chapter 6 Displaying the Book Records

Table 6-10. (continued)


3. 
cell.textLabel?.text = self.databaseFunctions.
books[indexPath.row].bookname

The system-defined cell reference has a default UI Textlabel. We are setting that
with current book name. Notice the array reference indexPath.row – this will give
the current row number of the Book Array, iterating from the first row until the last.

4. return cell

the function demands return of the cell for display

5. }

Step 6: Run the program


If you Run the program now, it will show you the books you have added. I
have added a few books using our Add Display View Controller. Figure 6-6
shows the simulator screen.

157
Chapter 6 Displaying the Book Records

Figure 6-6. Simulator Displaying Books

Detailed Text Label


What if we want to display additional information on the table cell or
beautify the cell (change color, font, add image). The default stock App
that Apple provides is a customized table view (shown in Figure 6-7). The
background color is black, and it has a stock Ticker and company name
in two rows, performance graph in between, and the current market price
with green/red based on up or down on the right side of the cell.

158
Chapter 6 Displaying the Book Records

Figure 6-7. Sample Screen

We will learn in Part II about customizing table views. Table View Cell
by default allows us to add an additional detailed text label. In this section,
we will learn about the same.
In our example, we will show the author’s name as detailed text next to
the Book name. Replace the cellforRowAt code with the code in Table 6-11.

159
Chapter 6 Displaying the Book Records

Table 6-11. Table Cell Display Detailed Text


func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
l et cell = UITableViewCell(style: UITableViewCell.
CellStyle.subtitle, reuseIdentifier: "book")
c ell.textLabel?.text = self.databaseFunctions.
books[indexPath.row].bookname
c ell.detailTextLabel?.text = self.databaseFunctions.
books[indexPath.row].authorname
return cell
}
1. 
func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {

This is the default function which draws each cell of the table (We have learned this
before)

2. 
//let cell = tableView.dequeueReusableCell(withIdentifier:
"book", for: indexPath)

We commented the previous dequeueresuablecell function of the tableview as we


are going to replace it with the following statement, which is required for getting the
detailed text along with the normal cell label

3. 
let cell = UITableViewCell(style: UITableViewCell.
CellStyle.value1, reuseIdentifier: "book")

We are calling the built-in table function UITableViewCell. There are many styles
available which will provide the layout of the cell. We use value1, and mentioned the
identifier which we have defined during table creation

4. 
cell.textLabel?.text = self.databaseFunctions.
books[indexPath.row].bookname

There is no change to this line – this will display the book


(continued)

160
Chapter 6 Displaying the Book Records

Table 6-11. (continued)


5. c ell.detailTextLabel?.text = self.databaseFunctions.
books[indexPath.row].authorname

This is a new addition which will display the detailed Text, which in our case is the
author’s name.

6. return cell

And like before, we are returning the cell to draw on the table view display.

7. }

Figure 6-8. Display Book with Author Name

When we run our application, we now see the author’s name as shown in Figure 6-8.

161
Chapter 6 Displaying the Book Records

Note Play around with the style and see how the cell layout
changes.

Setting a Table Header


Our Table currently doesn’t have a header. Look at the stock Screen
capture. It has a Heading called “Stocks and the current date”. Headers
give more context about the data and increase readability. We will add a
Header Called “My Books”, and for that we need to add a few more built-­
in functions of Table View. Add the Code in Table 6-12 to the table view
function.

Table 6-12. Table Header Function Details


func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String? {
return "My Books"
}

1. 
func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String? {

This is built-in table view function. Notice it returns a string, which is the Title for the
Table

2. return "My Books"

We returned a title called “My Books”

3. }

(continued)

162
Chapter 6 Displaying the Book Records

Table 6-12. (continued)

Figure 6-9. Book Display Screen with Header

Run the program and you will see the Title MyBooks on the top, as shown in
Figure 6-9. In Section II, we will learn how to beautify the table’s Header.

163
Chapter 6 Displaying the Book Records

Summary
Another very important chapter, where we learned about core data display
functions. Table Views are the most convenient way to display data on an
iOS App. We learned about the Table View setup, core table view functions,
manipulating basic display labels in table view, and working with Table
Header. We also learned additional cloudkit functions to retrieve book
records asynchronously from the iCloud repository.
In the next chapter, we will learn how to delete a book record from the
repository.

164
CHAPTER 7

Deleting a Table
Record
We now have a fully functional table view. We can add a book and we can
display all books with their respective author’s name. In this chapter, we
will learn how to delete a book record. From a user interface perspective,
we will add the delete button to the table cell as a swipe gesture. So, when
the user swipes left it will show the delete option and clicking the button
will allow the user to delete the record.

Trailing Swipe Function


Swift UI Table View provides a function for swipe function recognition.
Add the code in Table 7-1 to your Display View Controller File.

© Shantanu Baruah and Shaurya Baruah 2023 165


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_7
Chapter 7 Deleting a Table Record

Table 7-1. Trailing Swipe Function


func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath:
IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style:
.destructive, title: "Delete", handler: { (action,
view, success) in
self.databaseFunctions.group.enter()
self.databaseFunctions.deleteBook(bookname: self.
databaseFunctions.books[indexPath.row].bookname,
authorname: self.databaseFunctions.books[indexPath.
row].authorname)
self.databaseFunctions.group.notify(queue: .main) {
self.databaseFunctions.books.remove(at:
indexPath.row)
self.bookTableView.reloadData()
}
})

deleteAction.backgroundColor = .red
deleteAction.image = UIImage(systemName: "trash")

return UISwipeActionsConfiguration(actions:
[deleteAction])
}

1. func tableView(_ tableView: UITableView,


trailingSwipeActionsConfigurationForRowAt indexPath:
IndexPath) -> UISwipeActionsConfiguration? {

This is a built-in function which implements the UI Gesture Swipe on the table cell.
Notice it requires UISwipeActionsConfiguration to be returned and provide reference
to the concerned table view and the cell details through the IndexPath variable.
(continued)
166
Chapter 7 Deleting a Table Record

Table 7-1. (continued)

2. let deleteAction = UIContextualAction(style: .destructive,


title: "Delete", handler: { (action, view, success) in

We are defining a variable deleteAction of type UIContextualAction style and


we are setting it to be destructive. This feature allows full swipe to automatically
perform the first action (please note we are defining only one action in this example.
The swipe can have many actions). The handler is used for completion of the task.
We will write our code for delete action in this block later.

3. })

Completion of the handler block of our delete action

4. deleteAction.backgroundColor = .red

setting the background color to Red

5. deleteAction.image = UIImage(systemName: "trash")

For the action display, we can either set a label or an image. In our code, we are
setting this to the system trash icon

6. return UISwipeActionsConfiguration(actions: [deleteAction])

Now that all references are done, we are returning the deleteAction as a
UISwipeActionsConfiguration. Notice, the action is a collection of arrays. We can
define multiple swipe actions and return it for display.

7. }
(continued)

167
Chapter 7 Deleting a Table Record

Table 7-1. (continued)

Figure 7-1. Simulator Swiping Left

Swiping Left will give the delete button. But clicking will not do anything as our
completion handler is empty. Next, we will write the code for delete. Figure 7-1
shows the simulator screen for Trailing left delete function.

168
Chapter 7 Deleting a Table Record

Deleting book from CloudKit Database


When the user clicks the delete button, we need to call a routine to delete
the selected book from the database. We need to first search for the book
we need to delete. We will perform a query to find our book with the book
and author name. Once we retrieve it, we will delete it from the repository.
Please note, Cloudkit functions are asynchronous, and we need to ensure
we put the right rail guards of wait to ensure it functions properly.
All our database functions are written in the DatabaseFunctions.swift
file. Open the file and add the code in Table 7-2.

Table 7-2. DeleteFunction


func deleteBook(bookname: String, authorname: String){
var taskRecords = [CKRecord.ID]()
let pred = NSPredicate(format: "bookname == %@ &&
authorname == %@", bookname, authorname)
let query = CKQuery(recordType: "Book", predicate:
pred)
let operation = CKQueryOperation(query: query)
let ckRecordZoneID = CKRecordZone(zoneName:
"SharedZone")
operation.zoneID = ckRecordZoneID.zoneID

CKContainer.default().privateCloudDatabase.add(
operation)
operation.recordFetchedBlock = { record in
taskRecords.append(record.recordID)
}
(continued)

169
Chapter 7 Deleting a Table Record

Table 7-2. (continued)


operation.queryCompletionBlock = { (cursor, error) in
DispatchQueue.main.async {
let modifyRecordsOperation = CKModifyRecords
Operation.init(recordsToSave: nil,
recordIDsToDelete: taskRecords)

m odifyRecordsOperation.modifyRecords
CompletionBlock = { records, recordIDs, error in
DispatchQueue.main.async {
if let error = error {
print(error)
}else{
self.syncQueue.async {
self.deleteBookNotes(bookname:
bookname)
}
}
}}
CKContainer.default().privateCloudDatabase.
add(modifyRecordsOperation)
}}
}
(continued)

170
Chapter 7 Deleting a Table Record

Table 7-2. (continued)


1. func deleteBook(bookname: String, authorname: String){

A custom-defined function which takes two input parameters: book name and the
author’s name.

2. var taskRecords = [CKRecord.ID]()

taskRecords variable will store all record IDs that are returned by the query for
deletion. Note, the Cloudkit identifies every record with a unique record ID.

3. let pred = NSPredicate(format: “bookname == %@ && authorname


== %@”, bookname, authorname)

We are defining the condition for the query. Please make sure the field name is
matching to what we have in the database.

4. let query = CKQuery(recordType: “Book”, predicate: pred)

We are preparing the query on the “Book” Type with the predicate we have defined
before.

5. let operation = CKQueryOperation(query: query)

The query variable is set as an operation

6. CKContainer.default().privateCloudDatabase.add(operation)
And we execute it on the user’s private cloud database. Note, once this function
is executed, we will be in wait mode (we get into wait mode when we call the
deleteBook function).

7. operation.recordFetchedBlock = { record in

Once the Query is executed, it returns to the record Fetched Block, which is a
predefined function of CKOperations.
(continued)

171
Chapter 7 Deleting a Table Record

Table 7-2. (continued)


8. taskRecords.append(record.recordID)

We will loop through the result sets and add all record Ids to the custom defined
taskRecords. Note, record is a variable of CK Record Type. As we loop through it,
the record contains the record that matches the condition set forth in the predicate
of the query.

9. }

Enclosure of the Record Fetched Block

10. operation.queryCompletionBlock = { (cursor, error) in

when the query is completed, it will come to the queryCompletionBlock. Note, this is
asynchronous. Cursor has all references to record, and if the query is unsuccessful,
the error variable will contain the failure reasons.

11. DispatchQueue.main.async {

We will again wait on the main thread as it is an asynchronous operation.

12. let modifyRecordsOperation = CKModifyRecordsOperation.


init(recordsToSave: nil, recordIDsToDelete: taskRecords)

Now we will use the CloudKit built-in function CKModifyRecordsOperation. Notice it


takes in two parameters an array of record IDs to save, and an array of record ID to be
deleted. Since we do not want to save any record, we supply nil as the parameter value
for the first input parameter, and taskRecords to the recordIDsToDelete parameter.

13. CKContainer.default().privateCloudDatabase.
add(modifyRecordsOperation)

Next, we execute it on the PrivateCloudDatabase


(continued)

172
Chapter 7 Deleting a Table Record

Table 7-2. (continued)

14. modifyRecordsOperation.modifyRecordsCompletionBlock = {
records, recordIDs, error in DispatchQueue.main.async {

Once the method is complete, it returns to modifyRecordsCompletionBlock of our


operations variable modifyRecordsOperation.

15. if let error = error {

If there is an error in execution, this part will be executed. Notice error is an input
variable.

16. print(error)

we will print the error on the console

17. }else{

If not, we have successfully deleted the record. The reference of the same, if
needed, are in records and Record IDs variable.

18. self.syncQueue.async {

the syncQueue on which we are waiting we will enter upon completion.

19. self.group.leave()

and leave the group. You will see where the control goes from here in the next
section.
20. }
21. }
22. }}
23. }}
24. }

173
Chapter 7 Deleting a Table Record

Summary
In the last three chapters, we learned about the CRUD functions to
manipulate iCloud databases. In this chapter, we learned about deleting
a book record. From a user display standpoint, we also learned a swipe
function to display user-friendly actions.
The next chapter will conclude Part I of our learning. The last chapter
in Part I will focus on how to do conditional search and retrieve only those
books that the user wants to see. We will also learn about dynamic search
functions using Text Field built-in Edit functions.

174
CHAPTER 8

Searching Data
Screen
This chapter will complete the basic structure of our App. Search is the
third and last tab on our screen. Users will be able to search a book by its
title or will be able to get all books searched by an author name. Also, the
search will be dynamic, as the user types the letter it will show the book
name displayed.

Create the Search View Controller


As we have done before for “Add Book” and “Display Book,” create a
search view controller as a cocoa touch class. We will name this file as
SearchBookViewController of type UIViewController. Once the file is
created, under Identity Inspector, attach the file to the Search tab in the
Main.storyboard to the newly created search view controller.
This completes the creation and setup of the Search View Controller
screen. Next, we will design our search screen.

Draw the Search Screen


Our search screen has two UI Objects, a text field, where users can
enter either the book or author name to search and a table that will be
dynamically built based on the search text.

© Shantanu Baruah and Shaurya Baruah 2023 175


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_8
Chapter 8 Searching Data Screen

Defining the UIObjects


Table 8-1 has the code for defining the UI Objects for the
Search Screen.

Table 8-1. UI Object


let searchTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskInto
Constraints = false
textField.backgroundColor = .white
textField.borderStyle = .roundedRect

textField.attributedPlaceholder =
NSAttributedString(string: "Search Terms", attributes:
[NSAttributedString.Key.foregroundColor: UIColor.gray])
textField.textColor = .black

return textField
}()

let searchTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints =
false
tableView.backgroundColor = UIColor.init(red: 255/255,
green: 253/255, blue: 208/255, alpha: 1)
tableView.layer.cornerRadius = 5
tableView.layer.borderWidth = 1
tableView.layer.borderColor = UIColor.black.cgColor
tableView.isScrollEnabled = false
return tableView
}()

Skipping the line-by-line explanation as we have gone through this before.

176
Chapter 8 Searching Data Screen

Setting up the table and the text field


Table 8-2 has the code to set up the relevant table and text fields for
showing the search screen.

Table 8-2. Setting Up the Table and Text Fields


func setup(){
.delegate = self
searchTableView.dataSource = self
searchTableView.register(UITableViewCell.self,
forCellReuseIdentifier searchTableView: "searchcell")
searchTableView.backgroundColor = UIColor.init(red:
202/255, green: 202/255, blue: 202/255, alpha: 1)
searchTextField.delegate = self
searchTextField.addTarget(self, action: #selector(text
FieldEditingChanged), for: .editingChanged)
searchTextField.addTarget(self, action: #selector(text
FieldEditingDidBegin), for: .editingDidBegin)
searchTextField.addTarget(self, action: #selector(text
FieldEditingDidEnd), for: .editingDidEnd)
}
1. func setup(){

Setup function will be called when the view controller is loaded

2. searchTableView.delegate = self
3. searchTableView.dataSource = self
4. searchTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "searchcell")
5. searchTableView.backgroundColor = UIColor.init(red: 202/255,
green: 202/255, blue: 202/255, alpha: 1)
Lines 2-4 we have explained before. If you need to learn more, please refer to
previous section.
(continued)
177
Chapter 8 Searching Data Screen

Table 8-2. (continued)


6. searchTextField.delegate = self

We need to set the delegate of the UI Text Field for search to the view controller.
Please note we need to include the UITextFieldDelegate in the class definition

7. searchTextField.addTarget(self, action: #selector(textFieldE


ditingChanged), for: .editingChanged)

For the Search Text Field, we need to add three built-in functions for drawing the
dynamic table population behavior. We can add a function to a UI Object using
addTarget method. It takes the target object (which is self), the name of the
function as #selctor (a custom name is given which we need to define later. In this
statement, the custom method name is textFieldEditingChanged), and the control
event, which is the system-defined name editingChanged is called when any
change happens in the text field. Every time the user types, this method is called.

8. searchTextField.addTarget(self, action: #selector(textFieldE


ditingDidBegin), for: .editingDidBegin)

editingDidBegin is a system-defined function that is called when the user first types.

9. searchTextField.addTarget(self, action: #selector(textFieldE


ditingDidEnd), for: .editingDidEnd)
editingDidEnd is called when the system focus goes away from the text field
10. }

Note, the system will show error after defining the setup functions, as it
requires the delegates to be included, and custom functions for text fields
and mandatory functions for tables to be defined.
Delegates to be included
The code described in Table 8-3 defines the delegates and data source
for the Search Display Table.

178
Chapter 8 Searching Data Screen

Table 8-3. Table Delegate and Data Source Definition


class SearchBookViewController: UIViewController, UITableViewD
elegate,UITableViewDataSource,UITextFieldDelegate {

Table View Delegate and Data Source and Text Field Delegate is included

Drawing the Screen


This code in Table 8-4 will draw the text field on the screen

Table 8-4. Drawing the Search Text Field on the Screen


func drawInputScreen(){
view.addSubview(searchTextField)
commonFunctions.setFieldLayout(mainField:
searchTextField, constraintField: view!, topAnchor: 60,
leftAnchor: 5, rightAnchor: -5, heightAnchor: 40)
}
1. func drawInputScreen(){

custom function to draw the text field on the screen, to be called when form is
loaded

2. view.addSubview(searchTextField)

adding the searchTextfield to the View

3. commonFunctions.setFieldLayout(mainField: searchTextField,
constraintField: view!, topAnchor: 60, leftAnchor: 5,
rightAnchor: -5, heightAnchor: 40)

Applying the relevant constraints to the Search Text Field


4. }

This code in Table 8-5 will draw the Table View on the screen. Initially,
the table will be invisible as this will be populated based on user search
criteria.

179
Chapter 8 Searching Data Screen

Table 8-5. Drawing the Table View on the Screen


func drawSearchTable(){
let tableHeight:CGFloat = CGFloat(databaseFunctions.
books.count * 40)
view.addSubview(searchTableView)
commonFunctions.setFieldLayout(mainField:
searchTableView, constraintField: searchTextField,
topAnchor: 60, leftAnchor: 5, rightAnchor: -5,
heightAnchor: tableHeight)
}
1. func drawSearchTable(){

custom function to draw the search display table on the screen. To be called when
user starts the search

2. let tableHeight:CGFloat = CGFloat(databaseFunctions.books.


count * 40)

calculating the height of the table. Height of the row is 40, multiplying it with total
number of books searched

3. view.addSubview(searchTableView)

adding the table to the View

4. commonFunctions.setFieldLayout(mainField: searchTableView,
constraintField: searchTextField, topAnchor: 60, leftAnchor:
5, rightAnchor: -5, heightAnchor: tableHeight)

Applying the relevant constraints to the search result table

5. }

180
Chapter 8 Searching Data Screen

Query for All Records to Enable Search


The function in Table 8-6 will be called when the view is loading. We
want to get all the available records in an array and search the array. This
is because querying the database every time user types will be resource
intensive and will make the application slow.

Table 8-6. Query Construct for Searching the User Searched Books
fileprivate func bookQuery(){
databaseFunctions.group.enter()
databaseFunctions.bookQuery()
databaseFunctions.group.notify(queue: .main) {
self.setup()
self.drawInputScreen()
}
}

1. fileprivate func bookQuery(){

Custom function to Get all Books in a class level variable

2. databaseFunctions.group.enter()

Entering the group to wait on the main thread. It will be released after the query is
completed

3. databaseFunctions.bookQuery()

this is the custom function defined in DatabaseFunctions file (will see the details in
the next section)
(continued)

181
Chapter 8 Searching Data Screen

Table 8-6. (continued)

4. databaseFunctions.group.notify(queue: .main) {

Waiting on the thread. This will be discussed in detail in the next section

5. self.setup()

Once the query is over, we will call the setup function defined before

6. self.drawInputScreen()

and draw the screen to have a Search Field for user to search
7. }
8. }

Querying the Repository


In the DatabaseFunctions Swift file, we will add a function to Query
the Book Database for all records. Table 8-7 has the relevant code to query
the repository.

182
Chapter 8 Searching Data Screen

Table 8-7. Querying iCloud Repository for User Searched Books


func bookQuery() {
let pred = NSPredicate(value: true)
let query = CKQuery(recordType: "Book", predicate: pred)
let operation = CKQueryOperation(query: query)
CKContainer.default().privateCloudDatabase.add(operation)
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
operation.zoneID = ckRecordZoneID.zoneID

operation.recordFetchedBlock = { record in
let book = Book()
book.bookname = record["bookname"]
book.authorname = record["authorname"]
book.genre = record["genre"]
book.status = record["status"]
self.books.append(book)
}
o peration.queryCompletionBlock = { [unowned self] (cursor,
error) in
DispatchQueue.main.async {
if let error = error {
print(error)
}else {
self.syncQueue.async {
self.group.leave()
}
}
}
}
}
(continued)

183
Chapter 8 Searching Data Screen

Table 8-7. (continued)


1. func bookQuery() {
custom function to Query the Book Database
2. let pred = NSPredicate(value: true)
The predicate is for forming conditions, when set to true it will retrieve all records.
3. let query = CKQuery(recordType: "Book", predicate: pred)
Setting the query with the predicate
4. let operation = CKQueryOperation(query: query)
Setting the Operations with the query
5. CKContainer.default().privateCloudDatabase.add(operation)
The operation is now executed against the user private database
6. operation.recordFetchedBlock = { record in
This block will be called when the query returns from its asynchronous wait. The
Record object will have all book details. We are looping through each one of them
7. let book = Book()
we are defining a local Book object (Defined in the DatabaseFunctions class)
8. book.bookname = record[“bookname”]
storing the book name from the record variable to the book object
9. book.authorname = record[“authorname”]
storing the author from the record variable to the book object
10. book.genre = record[“genre”]
storing the genre from the record variable to the book object
11. book.status = record[“status”]
storing the status from the record variable to the book object
(continued)
184
Chapter 8 Searching Data Screen

Table 8-7. (continued)


12. self.books.append(book)

appending the book record to the class level books object array

13. }
14. operation.queryCompletionBlock = { [unowned self] (cursor,
error) in

when the query is completed, this block will be called – notice this is asynchronous

15. DispatchQueue.main.async {

16. if let error = error {

a. print(error)

if error in executing the query the error will be printed on the console

17. }else {

a. self.syncQueue.async {

on the syncQueue, we will start the asynchronous process

b. self.group.leave()

leave the wait on the group. This will return to the calling function of this bookQuery
function

c. }
18. }
19. }
20. }
21. }

185
Chapter 8 Searching Data Screen

Text Field Events, Operations, and Display


So far, we have all the books stored in the books object of type Book. We
have drawn our screen with a Text Field where users can input search
terms and drawn a table (not visible now. Also, note we haven’t defined
the mandatory table functions yet, so the code will show error until we
finish the next section). Text field has three events.

• The first is when editing begins, this is the preparation


event. We will copy the entire book record to a
temporary variable. This is because we will replace the
main book record object with the user search records.

• The second is when editing changes, this is called every


time user types in the text field. Every partial string as
user types will be searched against the temporary book
object containing the book records. If the string matches
any part of the book or author, it is stored in the book
object and the table is reloaded to display the record.

• The third and the last one is called when editing ends,
this is for housekeeping; we will replace the book object
with the temporary book object so that it is reset to the
entire book list. This is required so that when the user
searches next time, it has all the books to search from.

Defining Book Type Variable


Table 8-8 has the variable definition details.

Table 8-8. Variable Definition


var searchbooks = [DatabaseFunctions.Book]()
1. var searchbooks = [DatabaseFunctions.Book]()

Define this variable of type Book (Which we have defined in DatabaseFunctions


class) at the Search View Controller class level. This variable will hold all the books.

186
Chapter 8 Searching Data Screen

Editing Begin
The code in Table 8-9 is about Text Field Edit Begin Function.

Table 8-9. Text Field Editing Begin Function


@objc func textFieldEditingDidBegin(sender: UITextField){
if(sender == searchTextField){
searchbooks = databaseFunctions.books
}
}

1. @objc func textFieldEditingDidBegin(sender: UITextField){

This function Is called when the Search Text field gets focus (become first
responder).

2. if(sender == searchTextField){

The sender is an input variable which has the reference to the current focus text
field. In this view controller, we only have one text field, but it is always a good
practice to check which Text field has triggered the event. Also note, we made the
association of the textfield to the event earlier in the setup.

3. searchbooks = databaseFunctions.books

When editing begins, we are storing the book class object to another variable of
type Book called searchBooks. As explained before, we use the books variable
for content display on Table View and we will replace its value with only the books
that match the search criteria. Please note, the books variable is populated with all
books when we query the database.
4. }
5. }

Editing Changed
The code in Table 8-10 is about Text Field Edit Changed Function.

187
Chapter 8 Searching Data Screen

Table 8-10. Text Field Editing Begin Function


@objc func textFieldEditingChanged(sender: UITextField) {
if(sender == searchTextField){
databaseFunctions.books = searchbooks
databaseFunctions.books = searchbooks.filter {
item in return item.bookname.lowercased().
contains(searchTextField.text!.lowercased())
}
databaseFunctions.books.append(contentsOf:
searchbooks.filter {
item in return item.authorname.lowercased().
contains(searchTextField.text!.lowercased())
})
removeTableConstraints()
drawSearchTable()
if(databaseFunctions.books.count > 0){
searchTableView.isHidden = false
searchTableView.reloadData()
}else{
searchTableView.isHidden = true
}
}
}
(continued)

188
Chapter 8 Searching Data Screen

Table 8-10. (continued)


1. @objc func textFieldEditingChanged(sender: UITextField) {
This function Is called every time user types any letter in the Search Text Field.
2. if(sender == searchTextField){
The sender is an input variable which has the reference of the current focus text
field. In this view controller, we only have one text field, but it is always a good
practice to check which Text field has triggered the event. Also note, we made the
association of the textfield to the event earlier in the setup.
3. databaseFunctions.books = searchbooks
we are assigning what we have in searchbooks to the books variable. Note, book
is used to display and for first time both variables will have all the books.
4. databaseFunctions.books = searchbooks.filter {
Filter is a default function of arrays. We are going to filter from the searchbooks
(which for the first time has all books). Filter can take an inline function, hence the
opening curly bracket. The result is stored in the databaseFunctions.books, as this
variable will be used to display the books in the table view.
a. item in return item.bookname.lowercased().
contains(searchTextField.text!.lowercased())
In the inline method, we are looping through each book object and checking if the
bookname contains the user inputted text phrase. Note that contains can search
for phrases anywhere in the string. Also, notice lowercased, which converts all user
inputted text and the book name to lowercase, as Search is case sensitive. Both
contains and lowercased are system-defined functions available for any string
literal.
5. }
(continued)

189
Chapter 8 Searching Data Screen

Table 8-10. (continued)

6. databaseFunctions.books.append(contentsOf: searchbooks.filter {
We are going to do the same filter again, but this time, it is for the author name. So
that the user can search for a book and author name at the same time. Also note, we
are appending as we are adding on the search from the previous book name search
a. item in return item.authorname.lowercased().
contains(searchTextField.text!.lowercased())
same as before, the only change is we are searching for the author 's name.
7. })
8. removeTableConstraints()
This is a custom function to remove the table from the screen as we will redraw the
table with new records.
9. drawSearchTable()
This will redraw the Search Table.
10. if(databaseFunctions.books.count > 0){
After searching, all records are stored in the DatabaseFunctions class books
object. The following code is executed if the books variable has records
a. searchTableView.isHidden = false
if the condition is true, we will make the Search Table View visible
b. searchTableView.reloadData()
reload the table to display the searched records
11. }else{
a. searchTableView.isHidden = true
if the condition is false, we will hide the Search Table View
12. }
13. }
14. }

190
Chapter 8 Searching Data Screen

Editing End
The code in Table 8-11 is about Text Field Edit End Function.

Table 8-11. Text Field Editing End Function


@objc func textFieldEditingDidEnd(sender: UITextField){
if(sender == searchTextField){
databaseFunctions.books = searchbooks
}
}
1. @objc func textFieldEditingDidEnd(sender: UITextField){
This function Is called when the editing ends on the Search Text Field (Resign First
Responder)
2. if(sender == searchTextField){
The sender is an input variable which has the reference of the current focus text
field. In this view controller, we only have one text field, but it is always a good
practice to check which Text field has triggered the event. Also note, we made the
association of the textfield to the event earlier in the setup
a. databaseFunctions.books = searchbooks
when the editing on the search text field ends, we will store all books back in the
“books” variable
3. }
4. }

Remove Constraints
If we need to redraw UI Objects, it is always a good practice to remove the
constraints of the objects; otherwise, we will get layout constraint errors.
Table 8-12 has the relevant details.

191
Chapter 8 Searching Data Screen

Table 8-12. Remove Constraints


func removeTableConstraints(){
searchTableView.removeConstraints(searchTableView.
constraints)
searchTableView.removeFromSuperview()
}
1. func removeTableConstraints(){
custom functions to remove table constraint
2. searchTableView.removeConstraints(searchTableView.
constraints)
First remove the table constraints
3. searchTableView.removeFromSuperview()
Then remove the table from its super view
4. }

Table Functions
The code in Tables 8-13 and 8-14 shows two functions which must be
implemented for table views. For this view, we will only need these two
functions to be defined.
Total number of rows to Display

192
Chapter 8 Searching Data Screen

Table 8-13. Table Row Display Functions


func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return databaseFunctions.books.count
}
1. func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
This is the built-in required function of Table View which lets the table view know
how many rows to be displayed. The number is returned as an integer value
2. return databaseFunctions.books.count
We are returning the total records of the books variable. Recall in the Text Editing
Changed as we filter based on search, we populate the “books” variable
3. }

Render the Display of each Cell in a Table View

Table 8-14. Table Display Cell Functions


func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.
CellStyle.value1, reuseIdentifier: "searchcell")
let attrsList = [NSAttributedString.Key.font : UIFont.
boldSystemFont(ofSize: 16), NSAttributedString.Key.
foregroundColor : UIColor.black]
let attributedStringList = NSMutableAttributedStri
ng(string: databaseFunctions.books[indexPath.row].
bookname, attributes:attrsList)
cell.textLabel?.attributedText = attributedStringList
(continued)

193
Chapter 8 Searching Data Screen

Table 8-14. (continued)

cell.detailTextLabel?.text = databaseFunctions.
books[indexPath.row].authorname
return cell
}

1. func tableView(_ tableView: UITableView, cellForRowAt


indexPath: IndexPath) -> UITableViewCell {

Built-in required table view function to render the row display. It returns a UI Table
View Cell Object.

2. let cell = UITableViewCell(style: UITableViewCell.CellStyle.


value1, reuseIdentifier: “searchcell”)

We are defining a UI Table View Cell. The first argument is style, we are using right
and left align. On right will be the main text (book name) on right it will be the
detailed text (Author Name). The second argument is an identifier of the cell. In the
setup function, we gave our cell identifier name as “searchcell”

3. let attrsList = [NSAttributedString.Key.font : UIFont.


boldSystemFont(ofSize: 16), NSAttributedString.Key.
foregroundColor : UIColor.black]
Defining an attribute style with bold font 16 with color black.

4. let attributedStringList = NSMutableAttributedString(s


tring: databaseFunctions.books[indexPath.row].bookname,
attributes:attrsList)

retrieving the current book name based on the “indexPath” object row variable,
(This will loop through each available book in the array) and applying the attribute
style we created in the previous step
(continued)

194
Chapter 8 Searching Data Screen

Table 8-14. (continued)


5. cell.textLabel?.attributedText = attributedStringList
setting the Left side Text with the attribute Text parameter, which can take attributed
String
6. cell.detailTextLabel?.text = databaseFunctions.
books[indexPath.row].authorname
setting the cell detailed text label with author name
7. return cell
returning the cell to display the record
8. }

Run the program and it should show a screen like in Figure 8-1.

195
Chapter 8 Searching Data Screen

Figure 8-1. Simulator Search Screen

The first screen in Figure 8-1 is the display screen which shows all books.
The second screen in Figure 8-1 is the search screen. Notice the letter Typed is “T”,
the result shows every book or author that has the letter “T” or “t”.
The Third screen in Figure 8-1 we typed an e as well and it displays any book or
author that has “te” or “Te” or “TE” or “tE”

Summary
In this chapter, we learned how to conditionally search iCloud records and
display them for further action. We also learned how to make dynamic
search work using the Text Field Edit functions.

196
Chapter 8 Searching Data Screen

This chapter concludes our Part I of the book. If you have reached
here and tried everything written so far, you have learned the following
concepts:

• Familiarized with Xcode and iCloud environment setup

• Create UI Objects using Code

• Ability to execute CRUD functions on iCloud


Repository

• Table View manipulations for screen design and


data display

• Create a basic App with core functionality

197
CHAPTER 9

App Development
Part 2 Overview
If you have reached here, congratulations. You have acquired the required
basic knowledge to create your own iPhone App and now are some more
advanced tasks. That is quite a feat, and you should be proud to make
it thus far. What you have learned until now are the core skills, but not
enough for creating a professional-looking App. Besides, there are many
features that Xcode offers to make your App well integrated into the
Apple ecosystem. In this short chapter, we’ll review what lies ahead in the
remainder of the book and then start our iCloud set-up.

What Lies Ahead . . .


Here is a brief outline of what we are going to learn:

• First things first, we will uplift the UI. The UI we


designed in Part I was rudimentary, focusing more on
the concepts and less on the aesthetics. UI is a crucial
part of any App design. Ease of use, intuitive design, and
right layout/color are some of the important elements
we need to give importance to while developing an App.
In this section, we will spend a great deal of time making
our Book Tracker App look professional.

© Shantanu Baruah and Shaurya Baruah 2023 199


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_9
Chapter 9 App Development Part 2 Overview

• We will add new functionality. To make the Book


Tracker App usable, we need to enrich its function, and
by doing that, you will learn many coding nuances and
tricks. Functions such as editing a book, manipulating
search results, display book by status, validation if a
book exists before saving to avoid duplicate entry, auto
populated lists for ease of selection, incorporating date
are some of the features we will add to the App.

• We will enable sharing: if you want a group of books


to be shared with another book tracker user, you can
safely do it using iCloud Share Database concept.

• Notification Center: setting reminders. For


example, we can set a reminder to display a book
to read on a particular date and time on our iPhone
notification screen.

• Test Flight: when your App is ready to be released, you


may like to do a closed group beta testing; Test Flight
will allow you to enable the same.

• Publishing an App: finally, once ready, you can push


the App to the App Store for Apple review and approval.

iCloud Setup
Before we begin designing our App, let us go ahead and define the tables to
persist our data. We will need the following two tables:

200
Chapter 9 App Development Part 2 Overview

Table Name Description

Book This will store all book records ( This was explained in Part I)
Booknotes This table will store book notes by for each relevant Book

Figure 9-1 shows the Book table attributes and indexes (indexes are
important for enabling querying, sorting, and searching).

Figure 9-1. Book Table Attributes and Indexes

The Book notes table shown in Figure 9-2 will have the following
attributes and indexes (indexes are important for enabling querying,
sorting, and searching).

Figure 9-2. Book Notes Attributes and Indexes

201
Chapter 9 App Development Part 2 Overview

Summary
This chapter provided us with an introduction to Part II of our App
development journey. The chapter outlined everything we would learn
in the next 12 chapters. In the next part of our App creation journey, we
would need an additional table, called Book Notes, to persist all notes
about a book in iCloud repository – the chapter provided the details of the
Book Notes table.

202
PART II

Overview
CHAPTER 10

Redesigning the
Display Screen
In this chapter, we will redesign the screen to uplift the UI, as shown in
Figure 10-1.

© Shantanu Baruah and Shaurya Baruah 2023 205


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_10
Chapter 10 Redesigning the Display Screen

”‡˜‹‘—•‡”•‹‘ ‡™‡”•‹‘

Figure 10-1. Simulator Screen Comparison

The blocks on the Top will show the total books available by reading
status. The block shows an icon, a reading status title, and the total
count. Following immediately, we have all books displayed nicely with
customized cells. We will have an option of making a book Favorite; in
such a case, it will be shown under the favorite book list below the books

206
Chapter 10 Redesigning the Display Screen

by status block. Also, note we have added a new Tab bar icon “Shared
Books”, which will show books shared by others. We will now learn how to
create this screen. The new concepts we will learn are as follows:

• Manipulating Views

• UI Image View control

• Customizing table cells

 edesigning the UI of the Display


R
Book Screen
Initial Setup
Execute the following steps. You can refer to Part I for more details on how
to execute the following steps:

• We will rewrite the DisplayViewController. Instead of


deleting the old file, create a new view controller and
name it as BooksViewController.

• Go to main storyboard, and for the Display


View Controller re-associate the class to
BooksViewController in the Custom Class
Property tab.

Defining UI Objects for the Top Views


Each top block contains four elements, an UI View for defining the block
frame, an UI Image View to display the icon for the block, a UI title label for
the text, and another UI Text label for displaying the count. Table 10-1 has
the relevant code details.

207
Chapter 10 Redesigning the Display Screen

Table 10-1. UI Objects


lazy var readButton:UIButton = {
let uiButton = UIButton()
uiButton.translatesAutoresizingMaskIntoConstraints = false
uiButton.layer.cornerRadius = 5
uiButton.layer.borderWidth = 2
uiButton.layer.borderColor = UIColor.blue.cgColor
uiButton.layer.shadowRadius = 5
uiButton.layer.shadowOffset = .zero
uiButton.layer.shadowOpacity = 1
uiButton.addTarget(self, action:
#selector(readButtonAction), for: .touchUpInside)
return uiButton
}()
let readImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "note.text")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()
let readTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .right
return label
}()
(continued)

208
Chapter 10 Redesigning the Display Screen

Table 10-1. (continued)


let readDigitLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .right
return label
}()
1. Lazy var toBeReadButton:UIButton = {
2. let uiButton = UIButton()
3. uiButton.translatesAutoresizingMaskIntoConstraints = false
4. uiButton.layer.cornerRadius = 5
5. uiButton.layer.borderWidth = 1
6. uiButton.layer.borderColor = UIColor.black.cgColor
7. uiButton.layer.shadowRadius = 5
8. uiButton.layer.shadowOffset = .zero
9. uiButton.layer.shadowOpacity = 1
10. uiButton.addTarget(self, action: #selector(
tobeReadButtonAction), for: .touchUpInside)
11. return uiButton
12. }()
This first block defines an UIButton object for the first block (readButton). Notice
the border Width is set to 2 (higher than the other block buttons), and the border
color is set to blue. As this is the first view, we want to make it standout to show
all current displays of books with status “Read”. Also, there is a Touch Up Inside
button action, so when user clicks the button, the table view is refreshed to show
only books which are read
(continued)

209
Chapter 10 Redesigning the Display Screen

Table 10-1. (continued)

1. let readImageView: UIImageView = {


2. let theImageView = UIImageView()
3. theImageView.image = UIImage(systemName: "note.text")
4. t heImageView.translatesAutoresizingMaskIntoConstraints =
false
5. return theImageView
6. }()
Image View is to display the Icon to represent the Book. We have use System Image,
we can also put custom image drawn using tools such as Adobe and GIMP
7. let readTextLabel:UILabel = {
8. let label = UILabel()
9. label.translatesAutoresizingMaskIntoConstraints = false
10. label.backgroundColor = .white
11. label.numberOfLines = 4
12. label.textColor = .black
13. label.textAlignment = .right
14. return label
15. }()
UI Label for the title of the Block
16. let readDigitLabel:UILabel = {
17. let label = UILabel()
18. label.translatesAutoresizingMaskIntoConstraints = false
19. label.backgroundColor = .white
20. label.numberOfLines = 4
21. label.textColor = .black
22. label.textAlignment = .right
23. return label
24. }()
Another UI Label for showing the total counts in the “Read Category”

210
Chapter 10 Redesigning the Display Screen

The code in Table 10-2 is a repeat of the previous block to define the
next two top view blocks to show Books Currently Reading and Book To
be Read in the future. Please note, the system icons are changing, and the
view color is set to Gray.

lazy var readingButton:UIButton = {


let uiButton = UIButton()
uiButton.translatesAutoresizingMaskIntoConstraints = false
uiButton.layer.cornerRadius = 5
uiButton.layer.borderWidth = 1
uiButton.layer.borderColor = UIColor.black.cgColor
uiButton.layer.shadowRadius = 5
uiButton.layer.shadowOffset = .zero
uiButton.layer.shadowOpacity = 1
u iButton.addTarget(self, action: #selector(readingButton
Action), for: .touchUpInside)
return uiButton
}()
let readingImageView: UIImageView = {
let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "book.fill")
theImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()
let readingTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4

211
Chapter 10 Redesigning the Display Screen

label.textColor = .black
label.textAlignment = .right
return label
}()
let readingDigitLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .right
return label
}()
//MARK: - Top View UI Object - TO-BE-READ BLOCK VIEW
lazy var toBeReadButton:UIButton = {
let uiButton = UIButton()
uiButton.translatesAutoresizingMaskIntoConstraints = false
uiButton.layer.cornerRadius = 5
uiButton.layer.borderWidth = 1
uiButton.layer.borderColor = UIColor.black.cgColor
u iButton.layer.borderColor = UIColor(red: 0/255,
green: 0/255, blue: 0/255, alpha: 1).cgColor
uiButton.layer.shadowRadius = 5
uiButton.layer.shadowOffset = .zero
uiButton.layer.shadowOpacity = 1
u iButton.addTarget(self, action: #selector(tobeReadButton
Action), for: .touchUpInside)
return uiButton
}()

212
Chapter 10 Redesigning the Display Screen

let toBeReadImageView: UIImageView = {


let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "books.vertical.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()
let toBeReadTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .right
return label
}()
let toBeReadDigitLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .right
return label
}()

213
Chapter 10 Redesigning the Display Screen

We will also define two table views for displaying books by status and
books marked as favorite:

Table 10-2. UI Objects


//MARK: - Table UI Object
private let bookTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints =
false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()
private let favBookTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints =
false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()

Lifecycle Method and Initial Setup


When the UI View is called first, we need to do a few initial setup functions,
as outlined in the following:

• We need to query the cloud repository for getting


all books.

• Set the Background color of the view to White.

214
Chapter 10 Redesigning the Display Screen

• Assign the Book Table Delegate and data source.

• Also, assign the definition of our custom cell (we will


discuss this shortly) for the table cell. (Note, so far you
have the Default UI Table View Cell offered by Swift)

Class-Level Variables
We will define two class-level variables to access functions from our
custom DatabaseFunctions class (for all iCloud CRUD functions)
and CommonFunctions(for screen drawing functions). Besides these
two variables, we will also define three arrays of type Book (Defined in
DatabaseFunctions class) to hold books for the three top views (Read,
Reading, and To-Be-Read). bookTitle variable stores the Title for the Book
View Table and the cloudKitTask variable is to accept books shared from
other users (we will discuss this in the latter part of this book). Table 10-3
has the relevant code details.

Table 10-3. Class-Level Variables


let commonFunctions = CommonFunctions()
let databaseFunctions = DatabaseFunctions()
// We will have three table views based on the Reading status.
The three following variables defined will hold the books by
status:
var read:[DatabaseFunctions.Book] = []
var toberead:[DatabaseFunctions.Book] = []
var reading:[DatabaseFunctions.Book] = []
var bookTitle = "Books Read"
var cloudKitTask:CKRecord?

215
Chapter 10 Redesigning the Display Screen

Lifecycle Method
We will use the Default View Will Appear method as our lifecycle method,
and simply call a custom function booksQuery() to get the books from the
iCloud repository. The code details are in Table 10-4.

Table 10-4. Lifecycle View Did Appear Method


override func viewDidAppear(_ animated: Bool) {
super.viewDidLoad()
self.booksQuery()
}

Query Books
In the DatabaseFunctions Class, we have a function we have written
before to get all books from the iCloud repository. Reference to the same
is given in Table 10-5. Note that it goes against the user private database
so, only the user-specific books are retrieved. Also note we are storing all
books which have the favorite value set to 1 in the favbooks Array.

Table 10-5. Book Query Function


func bookQuery() {
let pred = NSPredicate(value: true)
let query = CKQuery(recordType: "Book", predicate: pred)
let operation = CKQueryOperation(query: query)
CKContainer.default().privateCloudDatabase.add(operation)
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
(continued)

216
Chapter 10 Redesigning the Display Screen

Table 10-5. (continued)

operation.zoneID = ckRecordZoneID.zoneID
operation.recordFetchedBlock = { record in
let book = Book()
book.bookname = record["bookname"]
book.authorname = record["authorname"]
book.genre = record["genre"]
book.status = record["status"]
book.reminder = record["reminder"]
book.favorite = record["favorite"]
book.recordID = record.recordID
if(book.favorite == 1){
self.favbooks.append(book)
}
self.books.append(book)
}
operation.queryCompletionBlock = { [unowned self] (cursor,
error) in
DispatchQueue.main.async {
if let error = error {
print(error)
}else {
self.syncQueue.async {
self.group.leave()
}
}
}
}
}

217
Chapter 10 Redesigning the Display Screen

Back to our Books View Controller, we will define the Query Book
functions. In the query books function, we will asynchronously wait until
the iCloud finishes the query. Upon completion, we will create three
different variables of type Book (defined in DatabaseFunctions class), and
store the respective book details based on the book status (Read, Reading,
or To-Be-Read). Relevant code on Book Query is in Table 10-6.

Table 10-6. Book Query Function


fileprivate func booksQuery(){
self.databaseFunctions.books.removeAll()
databaseFunctions.group.enter()
databaseFunctions.bookQuery()
databaseFunctions.group.notify(queue: .main) {
self.read = self.databaseFunctions.books.filter {
book in book.status.trimmingCharacters(in:
.whitespaces).lowercased() == "read"
}
self.toberead = self.databaseFunctions.books.
filter {
book in book.status.trimmingCharacters(in:
.whitespaces).lowercased() == "to be read"
}
self.reading = self.databaseFunctions.books.filter {
book in book.status.trimmingCharacters(in:
.whitespaces).lowercased() == "currently
reading"
}
(continued)

218
Chapter 10 Redesigning the Display Screen

Table 10-6. (continued)


self.databaseFunctions.books = self.read
self.readButton.layer.borderWidth = 2
self.readButton.layer.borderColor = UIColor.blue.
cgColor
self.readingButton.layer.borderWidth = 1
self.readingButton.layer.borderColor = UIColor.
black.cgColor
self.toBeReadButton.layer.borderWidth = 1
self.toBeReadButton.layer.borderColor = UIColor.
black.cgColor
self.setup()
self.topScreen()
self.drawBookDisplay()
self.bookTableView.reloadData()
}
}
1. fileprivate func booksQuery(){
2. databaseFunctions.group.enter()
3. databaseFunctions.bookQuery()
4. databaseFunctions.group.notify(queue: .main) {
The custom functions asynchronously; wait till the query is performed to retrieve all
books
5. self.read = self.databaseFunctions.books.filter {
6. book in book.status.trimmingCharacters(in: .whitespaces).
lowercased() == "read"
7. }
(continued)

219
Chapter 10 Redesigning the Display Screen

Table 10-6. (continued)


Upon return, filter the books array in the DatabaseFunctions to get all books with
status “read” and store it in the class level variable “read” of custom class Book
Type defined in the DatabaseFunctions class. Note, we are deleting any trailing
whitespaces and converting the text to lowercase.
8. self.toberead = self.databaseFunctions.books.filter {
9. book in book.status.trimmingCharacters(in: .whitespaces).
lowercased() == "to be read"
10. }
Upon return, filter the “books” array in the DatabaseFunctions to get all books
with status “to be read” and store it in the class level variable “toberead” of
custom class Book Type defined in the DatabaseFunctions class. Note, we are
deleting any trailing whitespaces and converting the text to lowercase.
11. self.reading = self.databaseFunctions.books.filter {
12. book in book.status.trimmingCharacters(in: .whitespaces).
lowercased() == "currently reading"
13. }
Upon return, filter the books array in the DatabaseFunctions to get all books with
status “reading” and store it in the class level variable “reading” of custom class
Book Type defined in the DatabaseFunctions class. Note, we are deleting any
trailing whitespaces and converting the text to lowercase.
14. self.databaseFunctions.books = self.read
Since by default when the view is rendered, we want it to default to the read status
array. We are storing all books in the “read” variable to the books array defined
in the DatabaseFunctions class, which is used by our table view to display the
books. Later you will see based on the top view block selection the “books” variable
is updated with the related filtered books status variable (“read”, “reading”, or
“toberead”)
(continued)

220
Chapter 10 Redesigning the Display Screen

Table 10-6. (continued)

15. self.setup()
After the queries are set we will call our custom setup functions, Explained in the
next section
16. self.topScreen()
We will draw the top of the screen with three block views
17. self.drawBookDisplay()
We will draw the table for displaying the book titles
18. self.bookTableView.reloadData()
Reload the table to display all books
19. }
20. }

Setup Function
We will do two things in setup. Set the background color of the view to
white and set the delegate, data source, and cell definition for the main
table to display books, and the favorite table to display the books marked
as favorite. The Cell we have used so far is the default Cell definition of
table view (UITableViewCell), while for our favorite table we will use the
default cell, for the main book display, we will use a custom cell class. We
will learn about the custom cell in the Table Section. Also, note as soon as
we set the delegate and the data source for the table, we will get two errors.
First, we need to make the View Controller extend the Table View Delegate
and Table View Data Source. Second, there are two mandatory table
functions that we need to write. It is ok to have errors for now. The error
will go away after completing the table section. Table 10-7 has the relevant
code details.

221
Chapter 10 Redesigning the Display Screen

Table 10-7. Book Table Delegate and Data Source


func setup(){
view.backgroundColor = UIColor(displayP3Red: 255/255,
green: 255/255, blue: 255/255, alpha: 1)
bookTableView.delegate = self
bookTableView.dataSource = self
bookTableView.register(BookTableViewCell.self,
forCellReuseIdentifier: "book")
favBookTableView.delegate = self
favBookTableView.dataSource = self
favBookTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "favbook")
}
1. func setup(){
2. view.backgroundColor = .white
Setting the background color of the view to the color white.
3. bookTableView.delegate = self
4. bookTableView.dataSource = self
5. bookTableView.register(BookTableViewCell.self,
forCellReuseIdentifier: "book")
Notice the custom cell class BookTableViewCell. We will learn about how to create
this in the table section of the tutorial. For now, it will give you error as the custom
table cell is not defined yet.
6. favBookTableView.delegate = self
7. favBookTableView.dataSource = self
8. favBookTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "favbook")
9. }

222
Chapter 10 Redesigning the Display Screen

Drawing the Screen


This code in Table 10-8 describes the details on drawing the screen layout
for the main display.

Drawing the Top Screen


Table 10-8. Drawing the Top Part of the Display Screen
func topScreen(){
let fontText = UIFont.systemFont(ofSize: 12, weight:
.bold)
let attributesText: [NSAttributedString.Key: Any] = [
.font: fontText
]
let fontDigit = UIFont.systemFont(ofSize: 24, weight:
.bold)
let attributesDigit: [NSAttributedString.Key: Any] = [
.font: fontDigit
]
view.addSubview(readButton)
commonFunctions.setFieldLayout(mainField: readButton,
constraintField: view!, topAnchor: 60, leftAnchor: 10,
rightAnchor: -270, heightAnchor: 80)
view.addSubview(readImageView)
commonFunctions.setFieldLayout(mainField:
readImageView, constraintField: readButton, topAnchor:
5, leftAnchor: 5, rightAnchor: -85, heightAnchor: 20)
view.addSubview(readTextLabel)
(continued)

223
Chapter 10 Redesigning the Display Screen

Table 10-8. (continued)


commonFunctions.setFieldLayout(mainField:
readTextLabel, constraintField: readButton, topAnchor:
5, leftAnchor: 25, rightAnchor: -5, heightAnchor: 20)
readTextLabel.attributedText =
NSAttributedString(string: "Books Read", attributes:
attributesText)
view.addSubview(readDigitLabel)
commonFunctions.setFieldLayout(mainField:
readDigitLabel, constraintField: readButton,
topAnchor: 40, leftAnchor: 25, rightAnchor: -5,
heightAnchor: 20)
readDigitLabel.attributedText =
NSAttributedString(string: String(read.count),
attributes: attributesDigit)
view.addSubview(readingButton)
commonFunctions.setFieldLayout(mainField:
readingButton, constraintField: view!, topAnchor: 60,
leftAnchor: 135, rightAnchor: -140, heightAnchor: 80)
view.addSubview(readingImageView)
commonFunctions.setFieldLayout(mainField:
readingImageView, constraintField: readingButton,
topAnchor: 5, leftAnchor: 5, rightAnchor: -85,
heightAnchor: 20)
view.addSubview(readingTextLabel)
commonFunctions.setFieldLayout(mainField:
readingTextLabel, constraintField: readingButton,
topAnchor: 5, leftAnchor: 30, rightAnchor: -5,
heightAnchor: 20)
(continued)

224
Chapter 10 Redesigning the Display Screen

Table 10-8. (continued)


readingTextLabel.attributedText =
NSAttributedString(string: "Reading Now", attributes:
attributesText)
view.addSubview(readingDigitLabel)
commonFunctions.setFieldLayout(mainField:
readingDigitLabel, constraintField: readingButton,
topAnchor: 40, leftAnchor: 25, rightAnchor: -5,
heightAnchor: 20)
readingDigitLabel.attributedText =
NSAttributedString(string: String(reading.count),
attributes: attributesDigit)
view.addSubview(toBeReadButton)
commonFunctions.setFieldLayout(mainField:
toBeReadButton, constraintField: view!, topAnchor: 60,
leftAnchor: 260, rightAnchor: -20, heightAnchor: 80)
view.addSubview(toBeReadImageView)
commonFunctions.setFieldLayout(mainField:
toBeReadImageView, constraintField: view!, topAnchor: 65,
leftAnchor: 265, rightAnchor: -100, heightAnchor: 20)
view.addSubview(toBeReadTextLabel)
commonFunctions.setFieldLayout(mainField:
toBeReadTextLabel, constraintField: toBeReadButton,
topAnchor: 5, leftAnchor: 30, rightAnchor: -5,
heightAnchor: 20)
toBeReadTextLabel.attributedText =
NSAttributedString(string: "To Be Read", attributes:
attributesText)
(continued)

225
Chapter 10 Redesigning the Display Screen

Table 10-8. (continued)


view.addSubview(toBeReadDigitLabel)
commonFunctions.setFieldLayout(mainField:
toBeReadDigitLabel, constraintField: toBeReadButton,
topAnchor: 40, leftAnchor: 25, rightAnchor: -5,
heightAnchor: 20)
toBeReadDigitLabel.attributedText =
NSAttributedString(string: String(toberead.count),
attributes: attributesDigit)
}
1. func topScreen(){
This is the custom function to draw the top part of the screen. This will be called
after the book query is completed
2. let fontText = UIFont.systemFont(ofSize: 12, weight: .bold)
We are defining a custom font for our top view block titles (Read/Reading/To-Be
Read)
3. let attributesText: [NSAttributedString.Key: Any] = [
4. .font: fontText
5. ]
In the preceding three lines of code, we are defining a variable of Type Attributed
Text. Please note UILabel, which will be used to display text, takes attributed text
data type for custom font. Notice Attributed text is an array type. While we are only
setting font text, other attributes can be set as well.
6. let fontDigit = UIFont.systemFont(ofSize: 24, weight: .bold)
7. let attributesDigit: [NSAttributedString.Key: Any] = [
8. .font: fontDigit
9. ]
The preceding four lines are for the Number of books we will display by the Book
Status in the blocks. It is exactly like the code snippet before, the only difference
being that digit will have a larger font size.
(continued)

226
Chapter 10 Redesigning the Display Screen

Table 10-8. (continued)


10. view.addSubview(readButton)
11. c ommonFunctions.setFieldLayout(mainField: readButton,
constraintField: view!, topAnchor: 60, leftAnchor: 10,
rightAnchor: -270, heightAnchor: 80)
12. v iew.addSubview(readImageView)
13. c ommonFunctions.setFieldLayout(mainField: readImageView,
constraintField: readButton, topAnchor: 5, leftAnchor: 5,
rightAnchor: -85, heightAnchor: 20)
14. view.addSubview(readTextLabel)
15. c ommonFunctions.setFieldLayout(mainField: readTextLabel,
constraintField: readButton, topAnchor: 5, leftAnchor: 25,
rightAnchor: -5, heightAnchor: 20)
16. r eadTextLabel.attributedText = NSAttributedString(string:
"Books Read", attributes: attributesText)
17. view.addSubview(readDigitLabel)
18. c ommonFunctions.setFieldLayout(mainField: readDigitLabel,
constraintField: readButton, topAnchor: 40, leftAnchor: 25,
rightAnchor: -5, heightAnchor: 20)
19. r eadDigitLabel.attributedText = NSAttributedString(string:
String(read.count), attributes: attributesDigit)
20. view.addSubview(readingButton)
21. c ommonFunctions.setFieldLayout(mainField: readingButton,
constraintField: view!, topAnchor: 60, leftAnchor: 135,
rightAnchor: -140, heightAnchor: 80)
22. view.addSubview(readingImageView)
23. c ommonFunctions.setFieldLayout(mainField: readingImageView,
constraintField: readingButton, topAnchor: 5, leftAnchor: 5,
rightAnchor: -85, heightAnchor: 20)
(continued)

227
Chapter 10 Redesigning the Display Screen

Table 10-8. (continued)


24. view.addSubview(readingTextLabel)
25. c ommonFunctions.setFieldLayout(mainField: readingTextLabel,
constraintField: readingButton, topAnchor: 5, leftAnchor: 30,
rightAnchor: -5, heightAnchor: 20)
26. r eadingTextLabel.attributedText = NSAttributedString
(string: "Reading Now", attributes: attributesText)
27. view.addSubview(readingDigitLabel)
28. c ommonFunctions.setFieldLayout(mainField: readingDigitLabel,
constraintField: readingButton, topAnchor: 40, leftAnchor:
25, rightAnchor: -5, heightAnchor: 20)
29. r eadingDigitLabel.attributedText = NSAttributedString
(string: String(reading.count), attributes:
attributesDigit)
30. view.addSubview(toBeReadButton)
31. c ommonFunctions.setFieldLayout(mainField: toBeReadButton,
constraintField: view!, topAnchor: 60, leftAnchor: 260,
rightAnchor: -20, heightAnchor: 80)
32. view.addSubview(toBeReadImageView)
33. c ommonFunctions.setFieldLayout(mainField: toBeReadImageView,
constraintField: view!, topAnchor: 65, leftAnchor: 265,
rightAnchor: -100, heightAnchor: 20)
34. view.addSubview(toBeReadTextLabel)
35. c ommonFunctions.setFieldLayout(mainField: toBeReadTextLabel,
constraintField: toBeReadButton, topAnchor: 5,
leftAnchor: 30, rightAnchor: -5, heightAnchor: 20)
(continued)

228
Chapter 10 Redesigning the Display Screen

Table 10-8. (continued)


36. t oBeReadTextLabel.attributedText = NSAttributedString
(string: "To Be Read", attributes: attributesText)
37. view.addSubview(toBeReadDigitLabel)
38. c ommonFunctions.setFieldLayout(mainField: toBeReadDigitLabel,
constraintField: toBeReadButton, topAnchor: 40,
leftAnchor: 25, rightAnchor: -5, heightAnchor: 20)
39. t oBeReadDigitLabel.attributedText = NSAttributedString
(string: String(toberead.count), attributes:
attributesDigit)
The preceding section code structure is explained in the first part of the book. We
are drawing the top three blocks of the screen showing the Total Book count by
three statuses – Read/Reading/To-Be Read
40. }

Draw Screen Main Display


The code in Table 10-9 will draw the Table view to display the books by
status, and a section to display the users’ favorite books.

229
Chapter 10 Redesigning the Display Screen

Table 10-9. Drawing the Top Part of the Display Screen


func drawBookDisplay(){
view.addSubview(bookTableView)
commonFunctions.setFieldLayout(mainField:
bookTableView, constraintField: view!, topAnchor: 150,
leftAnchor: 0, rightAnchor: 0, heightAnchor: 350)
view.addSubview(favBookTableView)
commonFunctions.setFieldLayout(mainField:
favBookTableView, constraintField: bookTableView,
topAnchor: 370, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 260)
}
1. func drawBookDisplay(){
This custom function draws the body of the screen, which will display the books for
the selected status and favorite books in a table view.
2. view.addSubview(bookTableView)
3. c ommonFunctions.setFieldLayout(mainField: bookTableView,
constraintField: view!, topAnchor: 150, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 350)
Drawing the Book Table View with a height of 350 pixels.
4. view.addSubview(favBookTableView)
5. c ommonFunctions.setFieldLayout(mainField: favBookTableView,
constraintField: bookTableView, topAnchor: 370,
leftAnchor: 0, rightAnchor: 0, heightAnchor: 260)
Drawing the Favorite Table View with a height of 350 pixels.
6. }

230
Chapter 10 Redesigning the Display Screen

Table Setup
Custom Cell
Defining a Custom Cell
In designing iPhone applications, table view plays a crucial role.
Aesthetically appealing screens usually have images, text with varying font
designs, graphs, and blocks. And in achieving it, table view cells play a
critical role. A Cell is a view. We can design this view to have any UI Object
organized and arranged to uplift the UI. In this example, we are creating a
simple view. We will have space between cells, and the Book name will be
centrally aligned within a raised button with a blue background.
As explained, a cell is a class. So, go ahead and create a class of Type
UITableViewCell and name it BookTableViewCell. See the screen details
in Figure 10-2 for reference.

Figure 10-2. Table View Cell Creation Option


231
Chapter 10 Redesigning the Display Screen

Add a Custom Button


Open the class and add a custom button at the class level. This will be
displayed as a row on the table view and will be populated with the book
name. The code details are in Table 10-10.

Table 10-10. Custom Button Code


let bookLabelButton:UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.contentEdgeInsets = UIEdgeInsets(top: 5, left:
5, bottom: 0, right: 0)
button.contentHorizontalAlignment = .center
button.contentVerticalAlignment = .center
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont.
boldSystemFont(ofSize: 16)
button.clipsToBounds = true
button.titleLabel?.numberOfLines = 5
button.titleLabel?.lineBreakMode = NSLineBreakMode.
byWordWrapping
button.backgroundColor = UIColor(displayP3Red: 89/255,
green: 140/255, blue: 217/255, alpha: 1)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
return button
}()
(continued)
232
Chapter 10 Redesigning the Display Screen

Table 10-10. (continued)


1. let bookLabelButton:UIButton = {
2. let button = UIButton()
3. button.translatesAutoresizingMaskIntoConstraints = false
4. b utton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5,
bottom: 0, right: 0)
5. button.contentHorizontalAlignment = .center
6. button.contentVerticalAlignment = .center
7. button.setTitleColor(.white, for: .normal)
8. button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
9. button.clipsToBounds = true
10. button.titleLabel?.numberOfLines = 5
11. b utton.titleLabel?.lineBreakMode = NSLineBreakMode.
byWordWrapping
Title label of button can have multiple lines with a defined font and size
12. b utton.backgroundColor = UIColor(displayP3Red: 89/255,
green: 140/255, blue: 217/255, alpha: 1)
13. button.layer.borderWidth = 1
14. button.layer.borderColor = UIColor.gray.cgColor
15. button.layer.cornerRadius = 5
16. b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
This is to give a shadow image to the button.
17. button.layer.shadowRadius = 5
18. button.layer.shadowOffset = .zero
19. button.layer.shadowOpacity = 1
The preceding lines of code will give the button a rounded rectangle on the edges.
20. return button
21. }()

233
Chapter 10 Redesigning the Display Screen

Define Class-Level Variables

let commonFunctions = CommonFunctions()

Draw the Cell Screen


Table 10-11 has the relevant code to draw the content within a Table Cell.

Table 10-11. Table Cell Custom Layout


override func setSelected(_ selected: Bool, animated:
Bool) {
super.setSelected(selected, animated: animated)
contentView.backgroundColor = .white
contentView.addSubview(bookLabelButton)
commonFunctions.setFieldLayout(mainField:
bookLabelButton, constraintField: contentView,
topAnchor: 0, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 40)
}
(continued)

234
Chapter 10 Redesigning the Display Screen

Table 10-11. (continued)


1. override func setSelected(_ selected: Bool, animated: Bool)
{
This is a system-defined function for cell class. This function is called every time the
cell is rendered
2. super.setSelected(selected, animated: animated)
3. contentView.backgroundColor = .white
we are setting up the cell with a white background
4. contentView.addSubview(bookLabelButton)
Adding the label Button that we have defined before
5. c ommonFunctions.setFieldLayout(mainField: bookLabelButton,
constraintField: contentView, topAnchor: 0, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 40)
Drawing the button in the cell. We have learned how to draw UI Objects in the first
part of this book.
6. }

Table View Class Inheritance


Let us now open the BooksViewController file. We need to inherit the
Table Delegate and Table Data source for enabling table functions for the
view controller. We have learned about this in detail in Part I. Refer to the
code in Table 10-12.

Table 10-12. Table Inheritance


class BooksViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource{

235
Chapter 10 Redesigning the Display Screen

Core Table Functions


We will implement four built-in functions of the UI Table view to set our
display.
Number of rows in Section – This will make the table view aware of
the number of book records to display. The relevant code is in Table 10-13.

Table 10-13. Table Function – Number of Rows in Section


func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
if(tableView == bookTableView){
return self.databaseFunctions.books.count
}else{
return self.databaseFunctions.favbooks.count
}
}
The total books count is returned to the table view. This will let the table view know
how many cells it needs to draw. The if condition checks for the table to draw and
returns the total count of records accordingly.

Cell for Row At – This function renders the cell with the book name.
This function is an iterative one and, depending on the number of rows
we have, the function will repeat to show the book rows. This is also
the function where we will refer to the custom cell and access the UI
Objects that we have defined in the cell definition. The code is defined in
Table 10-14.

236
Chapter 10 Redesigning the Display Screen

Table 10-14. Table Function – Cell for Row At


func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
if(tableView == bookTableView){
let cell = tableView.dequeueReusableCell(withIdenti
fier: "book", for: indexPath) as! BookTableViewCell
cell.bookLabelButton.setTitle(self.
databaseFunctions.books[indexPath.row].bookname,
for: .normal)
cell.bookLabelButton.addTarget(self, action:
#selector(taskButtonAction), for: .touchUpInside)
cell.bookLabelButton.tag = indexPath.row
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdent
ifier: "favbook", for: indexPath)
cell.accessoryType = .disclosureIndicator
cell.textLabel?.text = self.databaseFunctions.
favbooks[indexPath.row].bookname
return cell
}
}
1. f unc tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
System defined function to draw the cells
2. if(tableView == bookTableView){
if the tableview is for displaying the Book by status, the following statements will be
executed
(continued)

237
Chapter 10 Redesigning the Display Screen

Table 10-14. (continued)

3. l et cell = tableView.dequeueReusableCell(withIdentifier:
"book", for: indexPath) as! BookTableViewCell
The cell is defined using the table view Dequeue Reusable cell. System resources
are costly and dequeue cells help to repurpose the cell that goes off-screen. So, in
other words, if we have 100 books and the screen can only show 20 books, as the
user scrolls to display the next set of 20 books, the previously displayed cells are
repurposed as they go off screen. Also notice we are defining this as a custom cell
so adding as! (forcing) to Book Table View Cell, which we have defined before.
4. c ell.bookLabelButton.setTitle(self.databaseFunctions.
books[indexPath.row].bookname, for: .normal)
the custom cell we have defined has a button with the name bookLabelButton
(see the cell definition section). We are using the UI Button setTitle to set the title
of the button as the current row book name. (All books stored in books variable in
databaseFunctions class and the indexpath.row provide the current book index.)
5. c ell.bookLabelButton.addTarget(self, action:
#selector(taskButtonAction), for: .touchUpInside)
Notice the function taskButtonAction added to the button for .touchupinside event.
This function will be invoked when the button is pressed on each cell to show the
details of the book. For now, we will get an error as the function is not yet defined.
6. c ell.bookLabelButton.tag = indexPath.row
When the button on a cell is called, we need to know which book needs to be
displayed. Tag is an integer variable, and we are using this to store the current row
number to later retrieve the necessary book details
7. return cell
returning the cell for display
8. }else{
if the tableview is not for displaying the Book by status, the following statements will
be executed for showing the books marked as favorites
(continued)

238
Chapter 10 Redesigning the Display Screen

Table 10-14. (continued)


9. l et cell = tableView.dequeueReusableCell(withIdentifier:
"favbook", for: indexPath)
Notice we are using the default cell type here
10. cell.accessoryType = .disclosureIndicator
setting the cell accessorytype to discoloure indicator shows a > symbol at the end
of the record
11. c ell.textLabel?.text = self.databaseFunctions.
favbooks[indexPath.row].bookname
setting the display label with the favorite book name
12. return cell
returning the cell for display
13. }
14. }

Title for Header in Section – This will show the header of the table. We
will return a label based on which block is selected from the top part of the
screen (Books Read, Reading Now, and To Be Read. The relevant code is
defined in Table 10-15.

239
Chapter 10 Redesigning the Display Screen

Table 10-15. Table Function – Table Header Display


func tableView(_ tableView: UITableView,
titleForHeaderInSection section: Int) -> String? {
if(tableView == bookTableView){
return bookTitle
}else{
return "Favorite Books"
}
}
This is the class level variable which will hold the table header title. For Book display
by status, it will display the value of the bookTitle class variable. For Favorite books,
it will display “Favorite Books.”

Height for Row At: Since in our custom cell the height of the button
is 40 and we need to leave space between two cells, we want to make row
height more than the default 40 pixels. For the Favorite book it is set to the
standard 40 Pixel. The relevant code is defined in Table 10-16.

Table 10-16. Table Function – Table Row Height


func tableView(_ tableView: UITableView, heightForRowAt
indexPath: IndexPath) -> CGFloat {
if(tableView == bookTableView){
return 50.0
}
else{
return 40.0
}
}

240
Chapter 10 Redesigning the Display Screen

When the Favorite book cell is clicked, we need to show the details of
the book, as shown in Table 10-17.

Table 10-17. Table Function – Table Did Select Row


func tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath) {
if(tableView == favBookTableView){
let viewController = BookViewController()
viewController.delegateBooksViewRefresh = self
viewController.book = databaseFunctions.
favbooks[indexPath.row]
viewController.modalPresentationStyle =
.currentContext
viewController.modalTransitionStyle =
.crossDissolve
self.present(viewController, animated: true,
completion: nil)
favBookTableView.deselectRow(at: indexPath,
animated: true)
}
}
1. f unc tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath) {
Table View built-in function did Select Row At is called when the cell in a table is
selected
2. if(tableView == favBookTableView){
We will invoke this if the table is Favorite Table view. (Recall for Book Table view by
status, we have a custom cell so this function will not be called)
(continued)

241
Chapter 10 Redesigning the Display Screen

Table 10-17. (continued)

3. let viewController = BookViewController()


We are getting the handle of the BookViewController (We will discuss about this in
the latter part of the chapter to display detail of the selected book)
4. viewController.delegateBooksViewRefresh = self
Delegates can be used to pass values and invoke functions across view controllers.
We will use this delegate to update any changes done on the book detail view
controller back to our main display. In this case, the BookViewController has a
delegate variable with the name delegateBooksViewRefresh which confirms to
a custom protocol (RefreshBooksViewProtocol defined at the program level in
CommonFunctions class) and we are confirming it to the current class. We will
dedicate a section talking about delegates in the latter part of this book.
5. viewController.book = databaseFunctions.favbooks[indexPath.row]
the book that user selects is stored in the book variable of type Book in the
BookViewController class
6. viewController.modalPresentationStyle = .currentContext
As we are going to display the Book detail view controller, this defines how the
screen needs to be presented
7. viewController.modalTransitionStyle = .crossDissolve
As we are going to display the Book detail view controller, this defines how the
screen animation will be done
8. self.present(viewController, animated: true, completion: nil)
this will present the BookViewController
9. favBookTableView.deselectRow(at: indexPath, animated: true)
after the user comes back to the current view controller, we are deselecting the row,
otherwise it will remain highlighted from the last selection.
10. }
11. }

242
Chapter 10 Redesigning the Display Screen

Calling the Book Detail Screen When the Custom Cell Button
Is Clicked

@objc func taskButtonAction(sender: UIButton){


let viewController = BookViewController()
viewController.delegateBooksViewRefresh = self
viewController.book = databaseFunctions.books
[sender.tag]
viewController.modalPresentationStyle =
.currentContext
viewController.modalTransitionStyle = .crossDissolve
self.present(viewController, animated: true,
completion: nil)
}
The preceding code is the same when the favorite Book cell is clicked as explained
previously. The only difference is that it is called on the button .touchupinside
event.

Top View Blocks


Now that the table is defined, we will work on the top view. As we click
on the top three view blocks, the display table will change and the view
block will be highlighted with a blue color border shade, as shown in
Figure 10-3.:

243
Chapter 10 Redesigning the Display Screen

Figure 10-3. Book Display Screen – Top View – Book Status

244
Chapter 10 Redesigning the Display Screen

Figure 10-3. (continued)

View Button Action


This section, we will focus on clicking on the top views to change the table
to display the respective book records. Each of the top views is enclosed
in an UI Button. While defining the button (see section Defining UI
Objects for the Top Views), we have defined a button function on the
event touchupinside for each of the three views. We will implement those
functions here.

245
Chapter 10 Redesigning the Display Screen

Reading Button Action: This function is called when the user clicks
on the second view to show books that user is currently reading. Recall
when we queried the table, we filtered on the book records for status
“reading” and stored it in a variable “reading” of type Book class defined
in DatabaseFunctions class. The relevant code details are in Table 10-18.

Table 10-18. Top View Function – Currently Reading


@objc func readingButtonAction(action: UIButton){
self.databaseFunctions.books = reading
self.bookTitle = "Books Currently Reading"
bookTableView.reloadData()
readButton.layer.borderWidth = 1
readButton.layer.borderColor = UIColor.black.cgColor
readingButton.layer.borderWidth = 2
readingButton.layer.borderColor = UIColor.blue.cgColor
toBeReadButton.layer.borderWidth = 1
toBeReadButton.layer.borderColor = UIColor.black.cgColor
}

1. @objc func readingButtonAction(action: UIButton){


2. self.databaseFunctions.books = reading
Setting the books variable to the values of only the “reading” variable. The books
variable is used by the table view to display records. By making it set to only
reading, we ensure the table view can only show the books which the user is
currently reading.
3. self.bookTitle = "Books Currently Reading"
the Table View Header is changed to a new title
4. bookTableView.reloadData()
We need to reload the table to show the new set of records
5. readButton.layer.borderWidth = 1
(continued)

246
Chapter 10 Redesigning the Display Screen

Table 10-18. (continued)


6. readButton.layer.borderColor = UIColor.black.cgColor
for appearance, we want to make the first view (Read) border width 1 and border
color black
7. readingButton.layer.borderWidth = 2
8. readingButton.layer.borderColor = UIColor.blue.cgColor
our current view border should be a little thicker (2), and for a different color, we
have chosen blue.
9. toBeReadButton.layer.borderWidth = 1
10. toBeReadButton.layer.borderColor = UIColor.black.cgColor
for appearance, we want to make the third view (To Be Read) border width 1 and
border color black
11. }

Read Button Action: This function is called when the user clicks
on the First view to show books that user is currently reading. Recall
when we queried the table, we filtered on the book records for status
“read” and stored it in a variable “reading” of type Book class defined in
DatabaseFunctions class. The relevant code details are in Table 10-19.

247
Chapter 10 Redesigning the Display Screen

Table 10-19. Top View Function – Read


@objc func readButtonAction(action: UIButton){
self.databaseFunctions.books = read
self.bookTitle = "Books Read"
bookTableView.reloadData()
readButton.layer.borderWidth = 2
readButton.layer.borderColor = UIColor.blue.cgColor
readingButton.layer.borderWidth = 1
readingButton.layer.borderColor = UIColor.black.
cgColor
toBeReadButton.layer.borderWidth = 1
toBeReadButton.layer.borderColor = UIColor.black.
cgColor
}

To Be Button Action: This function is called when the user clicks on


the third view to show books that user is currently reading. Recall when
we queried the table, we filtered on the book records for status “to be
read” and stored it in a variable “reading” of type Book class defined in
DatabaseFunctions class. The relevant code details are in Table 10-20.

248
Chapter 10 Redesigning the Display Screen

Table 10-20. Top View Function – To Be Read


@objc func tobeReadButtonAction(action: UIButton){
self.databaseFunctions.books = toberead
self.bookTitle = "Books To Read"
bookTableView.reloadData()
readButton.layer.borderWidth = 1
readButton.layer.borderColor = UIColor.black.cgColor
readingButton.layer.borderWidth = 1
readingButton.layer.borderColor = UIColor.black.
cgColor
toBeReadButton.layer.borderWidth = 2
toBeReadButton.layer.borderColor = UIColor.blue.
cgColor
}

Custom Delegation
Delegations allow objects to communicate back to the source of its
origin in a decoupled way. Swift inherently uses Delegation for all its
objects – example is the return button on the Text field keyboard. When we
implement the default Text field delegate in a view controller, whenever
the return key is pressed, a system-defined delegate function is called, and
if it is implemented, the code is executed accordingly.
In this section, we are going to learn how to create a custom delegate.
Before we go ahead, let us understand the use case. In our App, from the
Main Display screen, user can tap on a book and can launch the Book
Detail screen where following book specific actions can be taken.

249
Chapter 10 Redesigning the Display Screen

• User can delete the book.

• Mark the book as favorite.

• Edit the Book.

• When user changes any of the preceding data, the Main


Display screen needs to be updated as follows.

• If the book is deleted, the Main Display should not


display the book any more.

• When a book is marked as favorite, it should appear in


the Favorite section of the Main Display Screen.

• If the status of book is changed between Read/


Currently Reading/To Be Read, the view should change
accordingly.

• Let us now go ahead and implement it.

Define the Delegate Protocol


Delegate is defined at a project level. We can choose any Class and put it
above the class definition. For increasing the readability, we are going to
put it in the CommonFunctions class. Table 10-21 has the relevant code
details.

250
Chapter 10 Redesigning the Display Screen

Table 10-21. Delegate Protocol Definition


protocol RefreshBooksViewProtocol {
func refreshData()
func refreshData(book: DatabaseFunctions.Book)
}
1. protocol RefreshBooksViewProtocol {
To define a protocol, we need to use the system-defined term protocol followed by a
custom function name
2. func refreshData()
3. func refreshData(book: DatabaseFunctions.Book)
The protocol will need a minimum of one function. We have defined two custom
functions that will be implemented in the Main Display when the control is returned
to refresh the view.
4. }

Implementing the Delegate


The next step is to implement the delegate in the owner class where the
delegate function will be executed once called from the child class. In
our example, the owner class is the Main Display class. We need to do
three things:

• Class definition is provided in Table 10-22, which


includes the delegate’s name.

Table 10-22. Class Extending the Delegate Protocol


class BooksViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource,
RefreshBooksViewProtocol {
RefreshBooksViewProtocol is the protocol name that we need to implement

251
Chapter 10 Redesigning the Display Screen

• Implement the delegate function(s) as defined in


Table 10-23.

Table 10-23. Delegate Protocol Implementation


//MARK: - Protocol Implementation
//This section is the implementation of the
Protocol functions of our custom-defined protocol
RefreshBooksViewProtocol
//RefreshData refreshes the view by querying the
repository for all new/modified records.
func refreshData() {
booksQuery()
}
// This is a shell implementation to confirm to the
protocol requirement
func refreshData(book: DatabaseFunctions.Book) {
}
As our protocol has two functions, notice that we need to implement both
the functions even though, in this class, we need only the first function. The
implementation is simple; when the function is called, it will in turn be called the
booksQuery( ) function. We have discussed this function before, it queries the
iCloud database for all books and refreshes the view. So, if there are changes made,
it will display those changes accordingly.

• Set the delegate reference on the calling object/class, as


defined in Table 10-24.

252
Chapter 10 Redesigning the Display Screen

Table 10-24. Delegate Protocol Reference Setup


@objc func taskButtonAction(sender: UIButton){
let viewController = BookViewController()
viewController.delegateBooksViewRefresh = self
viewController.book = databaseFunctions.books[sender.
tag]
viewController.modalPresentationStyle =
.currentContext
viewController.modalTransitionStyle = .crossDissolve
self.present(viewController, animated: true,
completion: nil)
}
func tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath) {
if(tableView == favBookTableView){
let viewController = BookViewController()
viewController.delegateBooksViewRefresh = self
viewController.book = databaseFunctions.
favbooks[indexPath.row]
viewController.modalPresentationStyle =
.currentContext
viewController.modalTransitionStyle =
.crossDissolve
self.present(viewController, animated: true,
completion: nil)
favBookTableView.deselectRow(at: indexPath,
animated: true)
}
}
(continued)

253
Chapter 10 Redesigning the Display Screen

Table 10-24. (continued)

We have discussed these two functions in the Main Display View controller before.
The first function is called when the user taps on the Book name in the main display
and the second function is called when the user taps a book in the favorite table.
In both the functions, the variable delegateBooksViewRefresh, which is of the
custom type RefreshBooksViewProtocol, is set to self, indicating the handle to the
delegate. This is important to get access to the delegate functions.

Calling the Delegate


The last step in the delegate is to call the delegate function. In our example,
we want to call the delegate function when the user is exiting the Book
Detail Screen. Following are the two steps we need to do in the Book Detail
View Controller (BookViewController) class file.

Define a Delegate Protocol Variable


Table 10-25 has the delegate protocol variable to handle delegate request.

Table 10-25. Delegate Protocol Reference Variable


var delegateBooksViewRefresh:RefreshBooksViewProtocol? = nil
This variable is required to get the handle to the delegate where the delegate
protocol is defined. Please note this is a class-level variable

Calling the Delegate Protocol Function


Table 10-26 has the delegate protocol call handling details.

254
Chapter 10 Redesigning the Display Screen

Table 10-26. Delegate Protocol Call


func deleteBook(){
let alertController = UIAlertController(title: "Delete
Book Action'', message: "Are you sure you want to
delete \(self.book.bookname ?? "")?", preferredStyle:
.alert)
let okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
self.databaseFunctions.deleteBook(bookname: self.
book.bookname, authorname: self.book.authorname)
self.databaseFunctions.group.enter()
self.commonFunctions.startSpinner(view: self.
view!, color: 1, spinner: self.spinner)
self.databaseFunctions.group.notify(queue: .main) {
self.commonFunctions.stopSpinner(spinner:
self.spinner)
self.delegateBooksViewRefresh?.refreshData()
self.dismiss(animated: true, completion: nil)
}
}
let cancelAction = UIAlertAction(title: "Cancel",
style: UIAlertAction.Style.cancel) {
UIAlertAction in
}
(continued)

255
Chapter 10 Redesigning the Display Screen

Table 10-26. (continued)


// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
self.present(alertController, animated: true,
completion: nil)
}
This function is called when the user selects to delete the book (We will learn about
it in the Book view section). After the book is deleted, the Book View Controller is
dismissed so that it can return to the parent view. Right before that, the delegate
protocol function is called
self.delegateBooksViewRefresh?.refreshData()
As discussed in the previous section, this will in turn call the Book Query function,
retrieving the books from iCloud and refreshing the view to display it.

Summary
In this chapter, we redesigned our Display Books View Controller.
Customizing Table View Cell to display other UI Objects was a critical part
of learning in this chapter. We also learned about Tap Gestures to add our
own events on UI Objects which don’t have built-in events. Finally, we
learned about delegates – as we move between screens, we need to pass
values across views – delegates help us to achieve the same.
In the next chapter, we will learn about revamping the Add
Book screen.

256
CHAPTER 11

Adding a Book
This chapter will provide details on how to add a book to our library. We
will create a new view controller for adding a book and link it to the bottom
tab bar, as shown in Figure 11-1.

>ŝŶŬ ƚŽ ƚŚĞ ĚĚ


ŽŽŬ

Figure 11-1. Add Book Tab Bar Option

© Shantanu Baruah and Shaurya Baruah 2023 257


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_11
Chapter 11 Adding a Book

The Add Book will take the following information from the user:

• Book Name as an Input field – while saving, it will check


for duplicate names

• Author Name – Name of the Author as free text

• Genre – This is a drop-down table providing a list of


Genre for the user to select

• Status – This is a drop-down table with three options


Read/Reading/To Be Read. This status is used to
display book by category

All inputs are mandatory. Save won’t work if any of the values is
missing. The screen layout for Adding a book is shown in Figure 11-2.
Execute the following steps to complete the functions.

258
Chapter 11 Adding a Book

Figure 11-2. Add Book Screen

 reation of View Controller and Linking It


C
to the Tab Bar
Create a new view controller with the name AddBookViewController and,
in the Main storyboard, link it to the Add Book Tab bar button. (See Part I
to learn how to link class to a tab.)

259
Chapter 11 Adding a Book

Inheriting Delegates
This view controller will inherit the table Delegate, table data source, and
Text Field Delegates. The Table delegate and data source is to display
a table for Genre and Status, and the Text Field delegate is to make the
keyboard disappear when return is pressed. Class inheritance code is in
Table 11-1.

Table 11-1. Class Inheritance


class AddBookViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource,
UITextFieldDelegate {

Declaring Variables
We will declare the following class variables as outlined in Table 11-2.

• DatabaseFunctions variable – to access all iCloud


functions and variables defined in our custom class

• CommonFunctions variable – to access screen


drawing function when iCloud asynchronous function
is performed

• CommonVariables variable - to access all common


variables such as Genre and Status

• A variable of Book Type to hold the newly created


book details

260
Chapter 11 Adding a Book

Table 11-2. Class Declarations


//MARK: - Class Variables
// the following three variables are used to access the
common class functions and variables
let commonFunctions = CommonFunctions()
let commonVariables = CommonVariables()
let databaseFunctions = DatabaseFunctions()

Declaring Screen Objects


This section is for the UI Object definition for the Add Book Screen. The
following UI Objects will be added to the screen:

• A Navigation bar with two buttons, Save and Reset, as


shown in Table 11-3.

Table 11-3. Add Book UI Objects


private let addNavigationBar:UINavigationBar = {
let navigationBar = UINavigationBar()
navigationBar.translatesAutoresizing
MaskIntoConstraints = false
navigationBar.backgroundColor = UIColor.init(red:
171/255, green: 189/255, blue: 217/255, alpha: 1)
let navItem = UINavigationItem(title: "Add a Book")
let resetSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 15, weight: .black)
let resetImage = UIImage(systemName: "arrowshape.
turn.up.left.fill", withConfiguration:
resetSymbolConfiguration)
(continued)

261
Chapter 11 Adding a Book

Table 11-3. (continued)


let editItem = UIBarButtonItem(image: resetImage,
landscapeImagePhone: resetImage, style: .plain,
target: nil, action: #selector(resetButtonAction))
let saveSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 20, weight: .black)
let saveImage = UIImage(systemName: "s.circle.fill",
withConfiguration: saveSymbolConfiguration)
let saveItem = UIBarButtonItem(image: saveImage,
landscapeImagePhone: saveImage, style: .plain, target:
nil, action: #selector(saveButtonAction))
navItem.rightBarButtonItem = saveItem
navItem.leftBarButtonItem = editItem
navigationBar.setItems([navItem], animated: false)
let image = UIImage(systemName: "s.circle.fill")
let imageView = UIImageView(image: image)
var imageViewFrame = imageView.frame
imageViewFrame.size.width = 200
imageViewFrame.size.height = 200
imageViewFrame.origin.x = 0
imageViewFrame.origin.y = 0
imageView.frame = imageViewFrame
return navigationBar
}()

• Two Input Text Field for Book and Author Name Input,
as shown in Table 11-4.

262
Chapter 11 Adding a Book

Table 11-4. Add Book UI Objects


private lazy var bookTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.attributedPlaceholder =
NSAttributedString(string: "Enter Book Name",
attributes: [NSAttributedString.Key.foregroundColor:
UIColor.white])
textField.textColor = .white
textField.addTarget(self, action: #selector(textFieldE
ditingDidBegin), for: .editingDidBegin)
return textField
}()
private lazy var authorTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.attributedPlaceholder =
NSAttributedString(string: "Enter Author Name",
attributes: [NSAttributedString.Key.foregroundColor:
UIColor.white])
textField.textColor = .white
textField.addTarget(self, action: #selector(textFieldE
ditingDidBegin), for: .editingDidBegin)
return textField
}()

263
Chapter 11 Adding a Book

• Two Display only Text Field to show the selected Genre


and Status, as shown in Table 11-5.

Table 11-5. Add Book UI Objects


private lazy var genreTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.isEnabled = false
textField.attributedPlaceholder =
NSAttributedString(string: "Select a Genre",
attributes: [NSAttributedString.Key.foregroundColor:
UIColor.white])
textField.textColor = .white
textField.addTarget(self, action: #selector(textFieldE
ditingDidBegin), for: .editingDidBegin)
return textField
}()
private lazy var statusTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .darkGray
textField.borderStyle = .roundedRect
textField.isEnabled = false
textField.attributedPlaceholder = NSAttributedString
(string: "Select a Status", attributes:
[NSAttributedString.Key.foregroundColor: UIColor.
white])
(continued)

264
Chapter 11 Adding a Book

Table 11-5. (continued)


textField.textColor = .white
textField.addTarget(self, action: #selector(textFieldE
ditingDidBegin), for: .editingDidBegin)
return textField
}()

• Two Table Views to display available Genre and Status,


as shown in Table 11-6.

Table 11-6. Add Book UI Objects


private let genreTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints =
false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()
private let statusTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints =
false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()

We have explained in Part I of this book how objects are defined.


Please visit Part I if you need to learn more about defining custom objects.

265
Chapter 11 Adding a Book

Screen Load Event and Initial Functions


We will use the default View Load function to initialize our screen. There
are two function we will call:

• Setup – this is for the initial setup. We will achieve the


following in the setup functions:

• Initialize the table views – set the delegate, Set the


Cell Reusable Identifier, and Style

• Get the Genre and Status

• Text Field delegate setup

• Draw the Screen with all variables

View Did Load


View Did Load function code details are in Table 11-7.

Table 11-7. View Did Load Function


/*
Leveraging the default viewDidLoad method to initiate
a custom function setup to perform initial setup and a
drawInputScreen function to draw the book Add screen
*/
override func viewDidLoad() {
super.viewDidLoad()
setup()
drawInputScreen()
}
Custom function setup and drawInputScreen are called from the View Did Load
function

266
Chapter 11 Adding a Book

Setup Function
View Did Load function code details are in Table 11-8.

Table 11-8. Setup Function


/*
The setup function will do the following:
- Setup the Genre and Ststus Table - both uses the
Default Cell Type
- Call the setGenres function to set all the Genres to be
displayed for user selection
- Call the setStatuses function to set all the Statuses
to be displayed for user selection
- Set the delegates for the Book and Author Text Fields
*/
func setup(){
genreTableView.delegate = self
genreTableView.dataSource = self
genreTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "genre")
genreTableView.layer.borderWidth = 0
genreTableView.layer.borderColor = UIColor.white.
cgColor
genreTableView.separatorStyle = .none
statusTableView.delegate = self
statusTableView.dataSource = self
statusTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "status")
statusTableView.layer.borderWidth = 0
(continued)

267
Chapter 11 Adding a Book

Table 11-8. (continued)

statusTableView.layer.borderColor = UIColor.white.
cgColor
statusTableView.separatorStyle = .none
commonVariables.setGenres()
commonVariables.setStatuses()
bookTextField.delegate = self
authorTextField.delegate = self
}

The Table setup we have gone through before, so we are going to skip the
explanation. We are also using the default Cell Type for the table cell.
There are two functions defined in the CommonVariables Calls, which will populate
the Genre and Status Array object. The following section will explain the functions

Genre and Status Objects


We will put the Genre and Status Objects in the CommonVariables class.
The code in Table 11-9 provides the steps in defining the Genre and Status
object and populating the respective variables.
• Create a Class called CommonVariables of type
NSObject.

• Define the following class level objects and variables.

268
Chapter 11 Adding a Book

Table 11-9. Class and Static Variable Definition


/*
Two Static Variable defined for the type of query to be
performed.
*/
static let Query_ALL = "All Shared"
static let Query_LIST = "List"
/*
Genre Class for defining Genre for the books
*/
class Genre{
var genrename:String!
var genredetail:[GenreDetail]!
}
class GenreDetail{
var genresubname:String!
}
/*
The Status class is about the Book Reading Statuses
*/
class Status{
var status:String!
}
/*
Array variables defined for storing genre and status
*/
var genres:[Genre] = []
var statuses:[Status] = []
(continued)

269
Chapter 11 Adding a Book

Table 11-9. (continued)


1. static let Query_ALL = "All Shared"
2. static let Query_LIST = "List"
Two static variables are defined, which will be referred to during querying of the
database to let the function know what object type we need to query.
3. class Genre{
4. var genrename:String!
5. var genredetail:[GenreDetail]!
6. }
7. class GenreDetail{
8. var genresubname:String!
9. }
Defining a class named “Genre.” One parent “Genre” can have multiple child
“Genre” objects. So, the “Genre” class has a Genre Name variable for the parent’s
name and a Genre Detail array of objects to store the child genre name. The Genre
Detail has a string genre name to store the child Genre.
10. class Status{
11. var status:String!
12. }
Defining a Status class to store the Book Status (Read/Reading/To Be Read)
13. var genres:[Genre] = []
Defining a “genres” array object of type Genre Class. This will store all genre names
to be used to populate the Genre Table
14. var statuses:[Status] = []
Defining a “statuses” array object of type Status Class. This will store all Statuses
to be used to populate the Status Table

• Set Genre Function, as shown in Table 11-10, for setting


up the Genre class object.

270
Chapter 11 Adding a Book

Table 11-10. Genre Setup Function


/*
setGenres sets the genres array with all identified genre
*/
func setGenres(){
var g = Genre()
g.genrename = "Fiction"
var gd = GenreDetail()
gd.genresubname = "Fantasy"
g.genredetail = [GenreDetail]()
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Dystopian"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Action & Adventure"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Mystery"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Horror"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Thriller & Suspense"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Historical Fiction"
g.genredetail.append(gd)
gd = GenreDetail()
(continued)

271
Chapter 11 Adding a Book

Table 11-10. (continued)

gd.genresubname = "Romance"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Women’s Fiction"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "LGBTQ+"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Contemporary Fiction"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Literary Fiction"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Magical Realism"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Science Fiction"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Graphic Novel"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Short Story"
g.genredetail.append(gd)
gd.genresubname = "Young Adult"
(continued)

272
Chapter 11 Adding a Book

Table 11-10. (continued)

g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "New Adult"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Children’s"
g.genredetail.append(gd)
gd = GenreDetail()
genres.append(g)
g = Genre()
g.genrename = "Non Fiction"
gd = GenreDetail()
gd.genresubname = "Memoir & Autobiography"
g.genredetail = [GenreDetail]()
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Biography"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Food & Drink"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Art & Photography"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Self-help"
g.genredetail.append(gd)
(continued)

273
Chapter 11 Adding a Book

Table 11-10. (continued)

gd = GenreDetail()
gd.genresubname = "History"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Travel"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "True Crime"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Humor"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Essays"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Guide or How-to"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Religion & Spirituality"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Humanities & Social Sciences"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Parenting & Families"
g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Science & Technology"
(continued)

274
Chapter 11 Adding a Book

Table 11-10. (continued)

g.genredetail.append(gd)
gd = GenreDetail()
gd.genresubname = "Children’s"
g.genredetail.append(gd)
genres.append(g)
}

This function stores the genre names against two main categories: Fiction and
Nonfiction.

• Set Status Function, as shown in Table 11-11, for setting


up the status class object.

Table 11-11. Status Setup Function


/*
setStatuses sets the statuses array with all identified
statuses
*/
func setStatuses(){
var s = Status()
s.status = "Read"
statuses.append(s)
s = Status()
s.status = "Currently Reading"
statuses.append(s)
s = Status()
s.status = "To Be Read"
statuses.append(s)
}
This function stores all three types of book status in the “Statuses” variable.

275
Chapter 11 Adding a Book

Draw Screen
Now, let us come back to our AddBookViewController class and add the
function as shown in Table 11-12.

Table 11-12. Draw Add Book Screen


/*
The drawInputScreen will draw the Add Book Screen with
all text fields and table views.
*/
func drawInputScreen(){
view.addSubview(addNavigationBar)
commonFunctions.setFieldLayout(mainField:
addNavigationBar, constraintField: view!, topAnchor:
60, leftAnchor: 0, rightAnchor: 0, heightAnchor: 40)
view.addSubview(bookTextField)
commonFunctions.setFieldLayout(mainField:
bookTextField, constraintField: addNavigationBar,
topAnchor: 60, leftAnchor: 5, rightAnchor: -5,
heightAnchor: 40)
view.addSubview(authorTextField)
commonFunctions.setFieldLayout(mainField:
authorTextField, constraintField: bookTextField,
topAnchor: 60, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 40)
view.addSubview(genreTextField)
commonFunctions.setFieldLayout(mainField:
genreTextField, constraintField: authorTextField,
topAnchor: 60, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 40)
view.addSubview(genreTableView)
(continued)

276
Chapter 11 Adding a Book

Table 11-12. (continued)

commonFunctions.setFieldLayout(mainField:
genreTableView, constraintField: genreTextField,
topAnchor: 40, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 200)
view.addSubview(statusTextField)
commonFunctions.setFieldLayout(mainField:
statusTextField, constraintField: genreTableView,
topAnchor: 220, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 40)
view.addSubview(statusTableView)
commonFunctions.setFieldLayout(mainField:
statusTableView, constraintField: statusTextField,
topAnchor: 40, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 200)
}

This function is called next after the setup function, which will draw the objects on
the screen as shown in the following.

277
Chapter 11 Adding a Book

Figure 11-3. Add Book screen

Displaying the Genre and Status Table


The following code will display the Genre and Status table view. The
following Table Functions are required to create the table view.

278
Chapter 11 Adding a Book

Table Sections
This code in Table 11-13 is required for the Genre Table. We have two
sections of Genre: Fiction and Non-Fiction. The following code instructs
the Genre Table View that there exist two display sections.

Table 11-13. Table Function – Number of Sections


/*
We have two tables but only the Genre will have section -
Fiction and Non Fiction. Note that we are returning 1 if
it is not the genre Table
*/
func numberOfSections(in tableView: UITableView) -> Int {
if(tableView == genreTableView){
return commonVariables.genres.count
}else{
return 1
}
}

Table Rows
Both tables need to inform the Table View how many rows are there
for Genre (in each section) and status table. The relevant code is in
Table 11-14.

279
Chapter 11 Adding a Book

Table 11-14. Table Function – Number of Rows in Section


/*
Number of rows for each section of the respective table
view is returned using this function
*/
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
if(tableView == genreTableView){
return commonVariables.genres[section].genredetail.
count
}else{
return commonVariables.statuses.count
}
}

280
Chapter 11 Adding a Book

Table Display Cell Value


This f code in Table 11-15 will display the respective genre and status on
the table view.

Table 11-15. Table Function – Cell for Row At


/*
cellForRowAt will display the values for the respective
genre or status table
*/
func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
if(tableView == genreTableView){
let cell = tableView.dequeueReusableCell(withIdent
ifier: "genre", for: indexPath)
cell.textLabel?.text = commonVariables.
genres[indexPath.section].genredetail[indexPath.
row].genresubname
return cell
}else{
let cell = tableView.dequeueReusableCell(withIdent
ifier: "status", for: indexPath)
cell.textLabel?.text = commonVariables.
statuses[indexPath.row].status
return cell
}
}

281
Chapter 11 Adding a Book

Table Row Height


The Height of each row of the table is defined in Table 11-16.

Table 11-16. Table Function – Row Height


/*
The Height of the Cell Row is set to default 40 pixels
*/
func tableView(_ tableView: UITableView, heightForRowAt
indexPath: IndexPath) -> CGFloat {
return 40.0
}

Table Header
We are going to design the Header of each table (Section Header) – this
code provides detail on the same. Notice that everything in a table is a
view. We are adding label objects to the header view and enhancing the
font and size for the header. Relevant code is in Table 11-17.

Table 11-17. Table Function – View for Header Section


/*
T he header for both the tables are set here. Note, the Genre
has two section - Fiction and Non Fiction
*/
f unc tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
if(tableView == genreTableView){
l et headerView = UIView.init(frame: CGRect.init(x: 0,
y: 0, width: tableView.frame.width, height: 50))
(continued)

282
Chapter 11 Adding a Book

Table 11-17. (continued)

h eaderView.backgroundColor = UIColor.init(red: 245/255,


green: 245/255, blue: 245/255, alpha: 1)
let label = UILabel()
l abel.frame = CGRect.init(x: 15, y: 0, width:
headerView.frame.width-10, height: headerView.frame.
height-10)
label.text = commonVariables.genres[section].genrename
label.textColor = .black
label.font = UIFont.boldSystemFont(ofSize: 18)
headerView.addSubview(label)
return headerView
}else{
l et headerView = UIView.init(frame: CGRect.init(x: 0,
y: 0, width: tableView.frame.width, height: 50))
h eaderView.backgroundColor = UIColor.init(red: 245/255,
green: 245/255, blue: 245/255, alpha: 1)
let label = UILabel()
l abel.frame = CGRect.init(x: 15, y: 0, width: headerView.
frame.width-10, height: headerView.frame.height-10)
label.text = "Status"
label.textColor = .black
label.font = UIFont.boldSystemFont(ofSize: 18)
headerView.addSubview(label)
return headerView
}
}

283
Chapter 11 Adding a Book

Table Header Height


The height of the header section is defined in Table 11-18.

Table 11-18. Table function -height for Header Section


/*
T he height of the Header section is set to default 40
Pixels.
*/
f unc tableView(_ tableView: UITableView, heightForHeader
InSection section: Int) -> CGFloat {
return 40.0
}

284
Chapter 11 Adding a Book

Table Select Row Action


When the user selects a genre or a status, we need to update the genre
read-only text field with the value. This event-based code defined in
Table 11-19 is used for achieving that function.

Table 11-19. Table Function – Did Select Row At


/*
W hen the user selects the value in the table views(genre and
status), the same is stored in the respective text field.
*/
f unc tableView(_ tableView: UITableView, didSelectRowAt
indexPath: IndexPath) {
if(tableView == genreTableView){
g enreTextField.text = commonVariables.genres[indexPath.
section].genrename + "::" + commonVariables.
genres[indexPath.section].genredetail[indexPath.row].
genresubname!
}else{
s tatusTextField.text = commonVariables.statuses
[indexPath.row].status
}
}

Input Text Field Events


The Text fields events are explained in this section. There are two events
which are important.

Visual Display
Code for Editing Did begin is defined in Table 11-20.

285
Chapter 11 Adding a Book

Table 11-20. Text Field – Did Editing Begin


/*
When Editing begins we aere making the border width to zero.
This is done to remove the red border, incase It was empty in
the previous save attempt
*/
@objc func textFieldEditingDidBegin(sender: UITextField){
sender.layer.borderWidth = 0
}
Editing Did Begin is an event which is triggered when the user starts inputting into
the text field (Book Name and Author Name) – the border will be colored red when
the input is blank or incorrect (existing book name) while saving the book. When
user starts entering the book name again, we are making the red color to go away
by making the border width to 0

Disappear the Keyboard Upon Return


Code for Text Field should Return is defined in Table 11-21.

Table 11-21. Text Field – Keyboard Should Return


/*
W hen the Return key is pressed on a keyboard it dismisses
the keyobard using the following event
*/
func textFieldShouldReturn(_ textField: UITextField) -> Bool{
textField.resignFirstResponder()
return false
}

286
Chapter 11 Adding a Book

Save the Book


In this code defined in Table 11-22, we will learn to save the book to the
iCloud repository. Before saving, the function checks if the fields are
empty, and if any of the inputs are missing it marks the text field with a red
border and puts the placeholder text with appropriate warning message.
The function also checks for duplicate book names and provides warnings
accordingly. If the book data is entered properly, it is saved to the iCloud
repository.

Table 11-22. Save Book Function – Validation


/*
This function is called when the user presses the save
button on the navigation bar. It checks if the input fields
are empty or not. If empty, it sets a variable canSave to
false. Make the border red and put respective placeholder text
in the input text field stating the respective field cannot
be blank. If all fields have value, it stores the text field
values to the class level defined book variable,and call the
saveBook function
*/
@objc func saveButtonAction(sender: UIButton){
var canSave:Bool = true
if(bookTextField.text == ""){
bookTextField.placeholder = "Book Name cannot be blank"
bookTextField.layer.borderWidth = 2
bookTextField.layer.cornerRadius = 5
bookTextField.layer.borderColor = UIColor.red.cgColor
canSave = false
(continued)

287
Chapter 11 Adding a Book

Table 11-22. (continued)

}else{
bookTextField.layer.borderWidth = 0
}
if(authorTextField.text == ""){
a uthorTextField.placeholder = "Author Name cannot be
blank"
authorTextField.layer.borderWidth = 2
authorTextField.layer.cornerRadius = 5
authorTextField.layer.borderColor = UIColor.red.cgColor
canSave = false
}else{
authorTextField.layer.borderWidth = 0
}
if(genreTextField.text == ""){
genreTextField.placeholder = "Genre cannot be blank"
genreTextField.layer.borderWidth = 2
genreTextField.layer.cornerRadius = 5
genreTextField.layer.borderColor = UIColor.red.cgColor
canSave = false
}else{
genreTextField.layer.borderWidth = 0
}
if(statusTextField.text == ""){
statusTextField.placeholder = "Status cannot be blank"
statusTextField.layer.borderWidth = 2
statusTextField.layer.cornerRadius = 5
statusTextField.layer.borderColor = UIColor.red.cgColor
canSave = false
(continued)

288
Chapter 11 Adding a Book

Table 11-22. (continued)

}else{
statusTextField.layer.borderWidth = 0
}
if(canSave){
book.bookname = bookTextField.text!
book.authorname = authorTextField.text!
book.genre = genreTextField.text!
book.status = statusTextField.text!
book.favorite = 0
saveBook()
}
}
1. @objc func saveButtonAction(sender: UIButton){
This function is linked to the save button of the Navigation Bar
2. var canSave:Bool = true
This variable will be set to false if the text fields are blank or the entered book name
is not accurate.
3. if(bookTextField.text == ""){
4. bookTextField.placeholder = "Book Name cannot be blank"
5. bookTextField.layer.borderWidth = 2
6. bookTextField.layer.cornerRadius = 5
7. bookTextField.layer.borderColor = UIColor.red.cgColor
8. canSave = false
9. }else{
10. bookTextField.layer.borderWidth = 0
11. }
(continued)

289
Chapter 11 Adding a Book

Table 11-22. (continued)

If the book name is blank – canSave variable is set to false, book saving is aborted,
and the book name text field border color is set to red and placeholder text states
“Book Name can’t be blank”
12. if(authorTextField.text == ""){
13. a uthorTextField.placeholder = "Author Name cannot be
blank"
14. authorTextField.layer.borderWidth = 2
15. authorTextField.layer.cornerRadius = 5
16. authorTextField.layer.borderColor = UIColor.red.cgColor
17. canSave = false
18. }else{
19. authorTextField.layer.borderWidth = 0
20. }
If the author’s name is blank – canSave variable is set to false, book saving is
aborted, and the author’s name text field border color is set to red and placeholder
text states “Author Name can’t be blank”
21. if(genreTextField.text == ""){
22. genreTextField.placeholder = "Genre cannot be blank"
23. genreTextField.layer.borderWidth = 2
24. genreTextField.layer.cornerRadius = 5
25. genreTextField.layer.borderColor = UIColor.red.cgColor
26. canSave = false
27. }else{
28. genreTextField.layer.borderWidth = 0
29. }
If the Genre is blank – canSave variable is set to false, book saving is aborted,
and the Genre name text field border color is set to red and placeholder text states
“Genre can’t be blank”
(continued)

290
Chapter 11 Adding a Book

Table 11-22. (continued)

30. if(statusTextField.text == ""){


31. statusTextField.placeholder = "Status cannot be blank"
32. statusTextField.layer.borderWidth = 2
33. statusTextField.layer.cornerRadius = 5
34. statusTextField.layer.borderColor = UIColor.red.cgColor
35. canSave = false
36. }else{
37. statusTextField.layer.borderWidth = 0
38. }
If the Status is blank – canSave variable is set to false, book saving is aborted, and
the Status text field border color is set to red and placeholder text states “Status
can’t be blank”
39. if(canSave){
40. book.bookname = bookTextField.text!
41. book.authorname = authorTextField.text!
42. book.genre = genreTextField.text!
43. book.status = statusTextField.text!
44. book.favorite = 0
45. saveBook()
46. }
If CanSave Variable is true – we populate the book class level variable with the user
entered/selected value and call the custom save book function
47. }

291
Chapter 11 Adding a Book

Saving to iCloud
The code defined in Table 11-23 will call the iCloud asynchronous function
to save the book record in the iCloud private repository of the user and
wait for completion.

Table 11-23. Save Book – iCloud Function Query


/*
T his function saves the book to the repository and post
saving to provides an alert that the book is saved.
*/
fileprivate func saveBook(){
databaseFunctions.group.enter()
databaseFunctions.saveBook(book:book)
databaseFunctions.group.notify(queue: .main) {
l et alertController = UIAlertController(title: "Save
Alert", message: "Book '\(self.book.bookname!)' is
Saved", preferredStyle: .alert)
// Create the actions
l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
self.tabBarController?.selectedIndex = 0
}
// Add the actions
alertController.addAction(okAction)
self.resetField()
(continued)

292
Chapter 11 Adding a Book

Table 11-23. (continued)

// Present the controller


s elf.present(alertController, animated: true,
completion: nil)
}
}

1. fileprivate func saveBook(){


Custom function to save the book
2. databaseFunctions.group.enter()
Waiting on the asynchronous loop
3. databaseFunctions.saveBook(book:book)
calling the save book function that will persist data in the iCloud – please see the
next section for details on saving the book
4. databaseFunctions.group.notify(queue: .main) {
After the book is saved to iCloud repository, it will notify the main thread, and we will
execute the following statements post saving.
5. l et alertController = UIAlertController(title: "Save
Alert", message: "Book '\(self.book.bookname!)' is Saved",
preferredStyle: .alert)
Declare an Alert Control, prompting user that saving is done.
6. // Create the actions
7. l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
8. UIAlertAction in
9. self.tabBarController?.selectedIndex = 0
10. }
The alert has only one button, “OK”, which, when pressed, we will go to the first tab
to display the books
(continued)

293
Chapter 11 Adding a Book

Table 11-23. (continued)

11. // Add the actions


12. alertController.addAction(okAction)
The Ok Action is added to the alert controller
13. self.resetField()
We will also call the reset field to empty all fields
14. // Present the controller
15. self.present(alertController, animated: true, completion: nil)
This presents the alert to the user.
16. }
17. }

Saving Book Record to iCloud


We will create a Class file DataBaseFunctions of type NSObject which will
have all functions required to save, update, query, delete Book Records.

Define Class Objects


We will define a class name Book, which will mimic our Book table in
iCloud. We will also define three variables of class Book to hold all books,
favorite books, and a single book instance. The class definition is in
Table 11-24.

294
Chapter 11 Adding a Book

Table 11-24. Book Class definition


/*
T he Class Book mimics the Book table we have created in our
iCloud Repository.
*/
class Book{
var bookname:String!
var authorname:String!
var genre:String!
var status:String!
var favorite:Int64!
var reminder:Date!
var recordID: CKRecord.ID!
}
/*
b ooks and favbooks are array variable of type Book. book is
a variable of type Book
*/
var books = [Book]()
var favbooks = [Book]()
var book = Book()

295
Chapter 11 Adding a Book

Define Class Variables


Define a group and a queue for Asynchronous Operations, as shown in
Table 11-25.

Table 11-25. Group and Queue Definition


//group and syncQueue are used to wait for the asynchronous
function to complete
let group = DispatchGroup()
l et syncQueue = DispatchQueue(label: "com.domain.app.
sections")

Define saveBook Function


The code in Table 11-26 will save the book to the private iCloud database
of the user. Note, every user will have their own copy of books.

Table 11-26. Save Book Record to iCloud


//MARK: - Save Book Record
/*
saveBook method will save a book record to a SharedZone
record zone. Please note sharedZone is created first if
it is not created. We need a custom zone for sharing
record to other users
*/
func saveBook(book:Book){
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
// Save the zone in the private database
let container = CKContainer.default()
let privateDB = container.privateCloudDatabase
(continued)

296
Chapter 11 Adding a Book

Table 11-26. (continued)

privateDB.save(ckRecordZoneID){ zone, error in


if let error = error{
print("Zone creation error: \(String(describing:
error))")
}else{
print("Zone created: \(zone!)")
}
}
let privateDatabase = CKContainer.default().
privateCloudDatabase
print(ckRecordZoneID.zoneID)
let ckRecordID = CKRecord.ID(zoneID: ckRecordZoneID.
zoneID)
let aRecord = CKRecord(recordType: "Book", recordID:
ckRecordID)
aRecord["bookname"] = book.bookname
aRecord["authorname"] = book.authorname
aRecord["genre"] = book.genre
aRecord["status"] = book.status
aRecord["favorite"] = book.favorite
privateDatabase.save(aRecord, completionHandler: {
(record, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print(error)
}
(continued)

297
Chapter 11 Adding a Book

Table 11-26. (continued)

else{
self.syncQueue.async {
self.group.leave()
}
}
}
})
}
1. func saveBook(book:Book){
The custom function to save the book to iCloud
2. let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
We will store this in a custom zone – we are naming this zone as SharedZone. This
is important if we want to share books with other users, as the default zone restricts
any sharing options.
3. // Save the zone in the private database
4. let container = CKContainer.default()
5. let privateDB = container.privateCloudDatabase
6. privateDB.save(ckRecordZoneID){ zone, error in
7. if let error = error{
8. print("Zone creation error: \(String(describing: error))")
9. }else{
10. print("Zone created: \(zone!)")
11. }
12. }
The preceding segment of code will create a SharedZone if it is not already existing.
13. let privateDatabase = CKContainer.default().
privateCloudDatabase
14. let ckRecordID = CKRecord.ID(zoneID: ckRecordZoneID.zoneID)
(continued)

298
Chapter 11 Adding a Book

Table 11-26. (continued)

15. l et aRecord = CKRecord(recordType: "Book", recordID:


ckRecordID)
From line 13–15, we are getting the handle to the private cloud database, getting
the zone ID information where the book will be stored, and then getting a handle to
the record type “Book” – please note this record type should match with the record
type defined in iCloud for storing the book record.
16. aRecord["bookname"] = book.bookname
17. aRecord["authorname"] = book.authorname
18. aRecord["genre"] = book.genre
19. aRecord["status"] = book.status
20. aRecord["favorite"] = book.favorite
we are setting the attribute of the book record with the details of the book entered
by user (notice we have passed a book record)
21. p rivateDatabase.save(aRecord, completionHandler: {
(record, error) -> Void in
22. DispatchQueue.main.async {
23. if let error = error {
24. print(error)
25. }
26. else{
27. self.syncQueue.async {
28. self.group.leave()
29. }
30. }
31. }
32. })
In the preceding segment of code, we are passing an asynchronous request to
save the book. When the control returns, if it is an error, we are printing the error on
console, if not, we are leaving the wait, which will then notify the parent queue in
the calling function.
33. }

299
Chapter 11 Adding a Book

Reset Fields
This is linked to the reset navigation bar tab bar button. As outlined in
Table 11-27, when clicked, it clears all input fields by assigning it an
empty string.

Table 11-27. Reset Function


/*
W hen resetField function is called it empties all input
fields and makes the book text field the first responder.
*/
func resetField(){
bookTextField.text = ""
authorTextField.text = ""
genreTextField.text = ""
statusTextField.text = ""
bookTextField.becomeFirstResponder()
}

Summary
In this chapter, we revamped our Add Book Screen. We learned how to
make a drop-down dynamic table view for Book Genre and Book Status
input fields. We also learned how to use Group Table view display when
we showed genre grouped under Fiction and Nonfiction in the Genre
TableView selection drop-down.
In the next chapter, we will display the details of the Book and action
we can take when a user taps on any book. This feature was not available in
our Basic App.

300
CHAPTER 12

Book Details View


Controller
This view controller is invoked when the user taps on a book either from
the Main Display by status table view or from the favorite table view. The
Book Detail view screen will have the following functionalities:

• Display the Book Details

• Share Book with other users

• Edit Book Details

• Delete the Book

• Allow User to add Book Notes


• Set Book Reminder – will be displayed on iPhone
notification center

• Mark the book as favorite

Initial Setup
Create the View Controller
Create a view Controller with the name “BookViewController.”

© Shantanu Baruah and Shaurya Baruah 2023 301


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_12
Chapter 12 Book Details View Controller

Class Inheritance
Beside the default UIViewController, we need to inherit the following
delegates:

• Table Delegate and Data Source delegate – this is for


two tables we will use in our Book details screen. The
first one to display the selected book record, and the
second table view for displaying Book Notes.

• A Custom defined Protocol


RefreshBooksViewProtocol (Refer to Delegate in the
previous section) for refreshing the book details once
the values are changed using Edit Book Details options.

Code outlined is in Table 12-1.

Table 12-1. Class Inheritance


class BookViewController: UIViewController, UITabBarDelegate,
UICloudSharingControllerDelegate, UITableViewDelegate,
UITableViewDataSource, RefreshBooksViewProtocol {

Class Variables
We will need the following types of variables

• Access to variables/functions from our custom


Common Functions and Database Function class.

• A variable of our custom class Book to hold the current


display book value.
• A Struct which will hold the current book details (Book
Name, Author Name, Genre, Reading Status).

302
Chapter 12 Book Details View Controller

• A variable of custom book detail struct type to hold the


book details to be iterated by table view for display on
the screen.

• A custom Protocol variable for the Book Detail View


Controller to communicate back to Main Display View
Controller.

• Code outlined is in Table 12-2.

Table 12-2. Class Variables


//MARK: - Class Variables
// the below two variables are used to access the common
class functions and variables

let commonFunctions = CommonFunctions()


let databaseFunctions = DatabaseFunctions()

// Book variable is to hold the Book details that will


be displayed on the screen. This is passed to the view
controller when the Book Detail screen is invoked.
var book = DatabaseFunctions.Book()

// Defining a Structure to hold the Book Details field in


a 2-Dimensional array. We will use a variable of this type
to show it in a table view.
struct BookDetails{
var label:String!
var value:[String]!
}
(continued)

303
Chapter 12 Book Details View Controller

Table 12-2. (continued)


// Variable of type BookDetails structure to hold all book
details in a 2D array.
var bookdetails:[BookDetails] = []

//Defintion od a protocol variable, to refresh the Main


Display screen when certain actions are taken on the book
(For example mark the book as favorite)
var delegateBooksViewRefresh:RefreshBooksViewProtocol? = nil

Initial Load Functions


We will use the default View Load Function to call out the initial setup
function and screen draw function, as shown in Table 12-3.

Setup
Table 12-3. Setup Function
/*
The setup function is used to do the following
- the background of the view is set to white
- setup the delegate for the tab bar
- set the book table view, which uses a custom cell for
displaying the book details.
- Call for a function setBookDetailswhcih will put the
book fields data in a two dimensional array
- Two Tap Gesture event defined for the reminder screen
button - save and reset
*/
(continued)

304
Chapter 12 Book Details View Controller

Table 12-3. (continued)


func setup(){
view.backgroundColor = .white
tabBar.delegate = self
bookTableView.delegate = self
bookTableView.dataSource = self
bookTableView.register(IconTableViewCell.self,
forCellReuseIdentifier: "task")
bookTableView.layer.borderWidth = 0
bookTableView.layer.borderColor = UIColor.white.
cgColor
bookTableView.separatorStyle = .none

setBookDetails()

let tapGestureRecognizerReminder =
UITapGestureRecognizer(target: self, action: #selector
(reminderImageTapped(tapGestureRecognizer:)))
reminderPopUpImageView.isUserInteractionEnabled = true

reminderPopUpImageView.addGestureRecognizer(tapGesture
RecognizerReminder)

let tapGestureRecognizerResetReminder =
UITapGestureRecognizer(target: self, action: #selector
(reminderResetImageTapped(tapGestureRecognizer:)))
reminderResetImageView.isUserInteractionEnabled = true

reminderResetImageView.addGestureRecognizer(tapGesture
RecognizerResetReminder)
}
(continued)

305
Chapter 12 Book Details View Controller

Table 12-3. (continued)

The setup function will perform the following:


• Set the background of the view controller to white.
• Set the Book Table Delegates.
• Create an array for displaying book details.
• Create a Tap Gesture for the Reminder setup pop view buttons (Save and
Reset). Reminder we will learn in the subsequent section.
1. func setup(){
This is our custom setup function
2. view.backgroundColor = .white
setting up the background of the view to white color
3. tabBar.delegate = self
tab bar delegate so that when user taps the button on the tab bar, relevant action is
taken
4. bookTableView.delegate = self
5. bookTableView.dataSource = self
6. b ookTableView.register(IconTableViewCell.self,
forCellReuseIdentifier: "task")
7. bookTableView.layer.borderWidth = 0
8. bookTableView.layer.borderColor = UIColor.white.cgColor
9. bookTableView.separatorStyle = .none
setting up the Table View Delegate, data source, Cell, and Style. Note we are using a
custom cell class IconTableViewCell
10. setBookDetails()
This is to set the book details array for the table view
11. l et tapGestureRecognizerReminder = UITapGestureRecognizer
(target: self, action: #selector(reminderImageTapped
(tapGestureRecognizer:)))
12. reminderPopUpImageView.isUserInteractionEnabled = true
(continued)

306
Chapter 12 Book Details View Controller

Table 12-3. (continued)

13. r eminderPopUpImageView.addGestureRecognizer(tapGesture
RecognizerReminder)
14. l et tapGestureRecognizerResetReminder = UITapGesture
Recognizer(target: self, action: #selector(reminderReset
ImageTapped(tapGestureRecognizer:)))
15. reminderResetImageView.isUserInteractionEnabled = true
16. r eminderResetImageView.addGestureRecognizer(tapGesture
RecognizerResetReminder)
Setting up the tap gesture event for the Save button and Reset button on the
reminder pop up. This is required because Image does not have any system-defined
events. The method reminderImageTapped is a custom method that needs to be
defined.
17. }

Set Book Details


Table 12-4 will set all the Book class data fields.

307
Chapter 12 Book Details View Controller

Table 12-4. Set Book Details Function


func setBookDetails(){
bookdetails.removeAll()
var value = BookDetails()
var detail:[String] = []
detail.append(book.bookname!)
value.label = "Book Name"
value.value = detail
bookdetails.append(value)

value = BookDetails()
detail = []
detail.append(book.authorname!)
value.label = "Author"
value.value = detail
bookdetails.append(value)

value = BookDetails()
detail = []
value.label = "Genre"

if(book.genre != nil){
let local = book.genre.split(separator: ",")

for val in local{


detail.append(String(val))
}
}

value.value = detail
bookdetails.append(value)
value = BookDetails()
detail = []
(continued)

308
Chapter 12 Book Details View Controller

Table 12-4. (continued)


detail.append(book.status!)
value.label = "Status"
value.value = detail
bookdetails.append(value)

bookTableView.reloadData()
}
The Table in the Book Detail view will show all the Book Data – Book Name, Author
Name, Genre, and Status. We want to show it like a table, and table loops through
an array object to display all the values. This function creates an array for all book
attributes. The array will have both the title and its respective value(s).
1. func setBookDetails(){
This is the custom function called from the previously explained setup function
2. bookdetails.removeAll()
This variable is defined at the class level, and it is of type BookDetails custom Struct
that we have defined at the class (See the variable section)
3. var value = BookDetails()
4. var detail:[String] = []
5. detail.append(book.bookname!)
6. value.label = "Book Name"
7. value.value = detail
8. bookdetails.append(value)
We are populating the Book name in the first occurrence of the variable. The First
row is the title of the attribute (in this case, it is “Book Name”) and the next row
is the actual book name. The temp variable is added to the class level bookDetails
variable.
9. value = BookDetails()
10. detail = []
11. detail.append(book.authorname!)
(continued)

309
Chapter 12 Book Details View Controller

Table 12-4. (continued)

12. value.label = "Author"


13. value.value = detail
14. bookdetails.append(value)
We are populating the Book Author as the next occurrence of the variable. The First
row is the title of the attribute (in this case, it is “Author Name”) and the next row is
the actual author’s name. The temp variable is added to the class level bookDetails
variable.
15. value = BookDetails()
16. detail = []
17. value.label = "Genre"
18. if(book.genre != nil){
19. let local = book.genre.split(separator: ",")
if we have more than one genre, it is stored separated by a comma in the database.
This following line of code will split the genre into an array by looking for the comma
as a separator.
20. for val in local{
21. detail.append(String(val))
22. }
23. }
24. value.value = detail
25. bookdetails.append(value)
We are populating the Genre as the next occurrence of the variable. The First row
is the title of the attribute (in this case, it is “Genre”) and the next row is the actual
genre name. Notice, in future we can have more than one genre for a book, hence
the loop through the genre. The temp variable is added to the class level bookDetails
variable.
26. value = BookDetails()
27. detail = []
(continued)

310
Chapter 12 Book Details View Controller

Table 12-4. (continued)

28. detail.append(book.status!)
29. value.label = "Status"
30. value.value = detail
31. bookdetails.append(value)
We are populating the Book Status as the final occurrence of the variable. The First
row is the title of the attribute (in this case, it is “Status”) and the next row is the
actual status. The temp variable is added to the class level bookDetails variable.
32. bookTableView.reloadData()
reloading the table to reflect the changes
33. }

Drawing the Screen


Once we complete the Book Detail code, the screen will appear like the
one shown in Figure 12-1.

311
Chapter 12 Book Details View Controller

Figure 12-1. Book Detail Screen

Screen Objects
The section provides the details of the UI Objects we will need to draw the
screen. The Objects are defined at the class level so that all methods can
access them when required. The Ui Object details of the Book Detail View
Controller are in Table 12-5.

312
Chapter 12 Book Details View Controller

Table 12-5. Book Details View Controller UI Objects


Type Description

UI Spinner The spinner shows a wait symbol when the screen is


refreshing
Tab Bar Bottom Tab Bar, which will allow user to exit the book detail
screen to return to the main display screen
Book Label This is for displaying the book title on the top

UI Object Code Snippets


Table 12-6 has the code to define the UI Objects.

313
Chapter 12 Book Details View Controller

Table 12-6. Book Details View Controller UI Objects Definition


let spinner:UIActivityIndicatorView = {
let spinner = UIActivityIndicatorView(style: .large)
spinner.translatesAutoresizingMaskIntoConstraints = false
return spinner
}()

private let tabBar:UITabBar = {


let tabbar = UITabBar()
tabbar.translatesAutoresizingMaskIntoConstraints = false
t abbar.backgroundColor = UIColor.init(red: 171/255, green:
189/255, blue: 217/255, alpha: 1)
l et saveSymbolConfiguration = UIImage.SymbolConfiguration
(pointSize: 24, weight: .black)
l et saveImage = UIImage(systemName: "s.circle.fill",
withConfiguration: saveSymbolConfiguration)
l et save = UITabBarItem(title: "Save and Exit", image:
saveImage, selectedImage: saveImage)
save.tag = 0
tabbar.setItems([save], animated: false)
return tabbar
}()

let bookLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()

314
Chapter 12 Book Details View Controller

Table 12-7 has details on all six blocks on the top of the Book detail
screen, each denoting a particular function.

Table 12-7. Six View Blocks UI Object Description


Type Description

Share Block This is to display the Share block. Button is for the block
Share Button frame, Image to display the Icon, and label to display the
Share Image View label text.
Share Text Label
Edit Block This is to display the Edit block. Button is for the block
Share Button frame, Image to display the Icon and label to display the
Share Image View label text
Share Text Label
Delete Block This is to display the Delete block. Button is for the block
Share Button frame, Image to display the Icon and label to display the
Share Image View label text
Share Text Label
Book Notes Block This is to display the Book Notes block. Button is for the
Share Button block frame, Image to display the Icon and label to display
Share Image View the label text
Share Text Label
Reminder Block This is to display the Reminder block. Button is for the block
Share Button frame, Image to display the Icon and label to display the
Share Image View label text
Share Text Label
Favorite Block This is to display the Favorite block. Button is for the block
Share Button frame, Image to display the Icon and label to display the
Share Image View label text
Share Text Label

315
Chapter 12 Book Details View Controller

UI Object Code Snippets


Table 12-8 has the all six block definition.

Table 12-8. Six View Blocks UI Object Definition


lazy var shareButton:UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.backgroundColor = .white
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
b utton.addTarget(self, action: #selector(shareButtonAction),
for: .touchUpInside)
return button
}()
(continued)

316
Chapter 12 Book Details View Controller

Table 12-8. (continued)

let shareImageView: UIImageView = {


let theImageView = UIImageView()
t heImageView.image = UIImage(systemName: "square.and.arrow.
up.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()

let shareTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()

lazy var editButton:UIButton = {


let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.backgroundColor = .white
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
(continued)
317
Chapter 12 Book Details View Controller

Table 12-8. (continued)

button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
b utton.addTarget(self, action: #selector(completeButton
Action), for: .touchUpInside)
return button
}()

let editImageView: UIImageView = {


let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "checkmark")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
theImageView.tag = 0
return theImageView
}()

let editTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()
(continued)

318
Chapter 12 Book Details View Controller

Table 12-8. (continued)

lazy var deleteButton:UIButton = {


let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.backgroundColor = .white
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
b utton.addTarget(self, action: #selector(deleteButton
Action), for: .touchUpInside)
return button
}()

let deleteImageView: UIImageView = {


let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "trash.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
theImageView.tintColor = .red
return theImageView
}()
(continued)

319
Chapter 12 Book Details View Controller

Table 12-8. (continued)

let deleteTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()

lazy var notesButton:UIButton = {


let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.backgroundColor = .white
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
b utton.addTarget(self, action: #selector(notesButton
Action), for: .touchUpInside)
return button
}()
(continued)

320
Chapter 12 Book Details View Controller

Table 12-8. (continued)

let notesImageView: UIImageView = {


let theImageView = UIImageView()
t heImageView.image = UIImage(systemName: "square.and.
arrow.up.on.square.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()

let notesTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()

lazy var reminderButton:UIButton = {


let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.backgroundColor = .white
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
(continued)

321
Chapter 12 Book Details View Controller

Table 12-8. (continued)


b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
b utton.addTarget(self, action: #selector(reminderButton
Action), for: .touchUpInside)
return button
}()

let reminderImageView: UIImageView = {


let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "lightbulb.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()

let reminderTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()
(continued)

322
Chapter 12 Book Details View Controller

Table 12-8. (continued)

lazy var favButton:UIButton = {


let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.clipsToBounds = true
button.backgroundColor = .white
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.gray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.black.cgColor
b utton.layer.shadowPath = UIBezierPath(rect: button.
bounds).cgPath
button.layer.shadowRadius = 5
button.layer.shadowOffset = .zero
button.layer.shadowOpacity = 1
b utton.addTarget(self, action: #selector(frequencyButton
Action), for: .touchUpInside)
return button
}()

let favImageView: UIImageView = {


let theImageView = UIImageView()
theImageView.image = UIImage(systemName: "calendar")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()
(continued)

323
Chapter 12 Book Details View Controller

Table 12-8. (continued)

let favTextLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()

Table 12-9 has the definition of the Table view, which will display the
Book detail and popup view for setting reminder.

Table 12-9. Table and Popup View Description


Type Description

Table View Table view for displaying the current Book Details (Book Name, Author
Name, Genre, and reading status)
Popup We will need this popup view when the user taps on the Reminder
View Block, this view will display a date control for setting the reminder.
Large The Large Popup view is used to cover the entire screen with a light
Popup gray shade to avoid users from accidentally clicking other action
View buttons on the backdrop screen when a reminder is displayed.

324
Chapter 12 Book Details View Controller

UI Object Code Snippets


Table 12-10 has the Table and Popup view definition.

Table 12-10. Table and Popup View Definition


//MARK: - Table UI Object
private let bookTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()

private let popUpView:UIView = {


let uiView = UIView()
uiView.translatesAutoresizingMaskIntoConstraints = false
uiView.backgroundColor = .white
uiView.layer.borderColor = UIColor.black.cgColor
uiView.layer.borderWidth = 1
uiView.layer.cornerRadius = 5
return uiView
}()
private let largerPopUpView:UIView = {
let uiView = UIView()
uiView.translatesAutoresizingMaskIntoConstraints = false
uiView.backgroundColor = .white
uiView.layer.borderColor = UIColor.black.cgColor
uiView.layer.borderWidth = 1
uiView.layer.cornerRadius = 5
return uiView
}()

325
Chapter 12 Book Details View Controller

Table 12-11 has the definition of the Reminder popup screen UI


Objects.

Table 12-11. Reminder Popup Screen Description


Type Description

Reminder Label This is for the Reminder popup screen to set the reminder.
This is a UI label to display the reminder prompt text
Reminder Picker Date Picker for setting the reminder
Reminder Image View This button will save the reminder for the book in the
iCloud repository.
Remind Rest Image This is the reset button to set the date to a back date so
View that no reminder is set (reminder works always for a future
date)

UI Object Code Snippets


Table 12-12 has the Reminder Popup Screen definition.

Table 12-12. Has the Reminder Popup Screen Definition


//MARK: - Reminder
let reminderLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()
(continued)

326
Chapter 12 Book Details View Controller

Table 12-12. (continued)

private let reminderdatePicker:UIDatePicker = {


let datePicker = UIDatePicker()
d atePicker.translatesAutoresizingMaskIntoConstraints =
false
datePicker.datePickerMode = .dateAndTime
d atePicker.layer.borderColor = UIColor(displayP3Red:
93/255, green: 117/255, blue: 156/255, alpha: 1).cgColor

return datePicker
}()
let reminderPopUpImageView: UIImageView = {
let theImageView = UIImageView()
t heImageView.image = UIImage(systemName: "checkmark.
square.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()

let reminderResetImageView: UIImageView = {


let theImageView = UIImageView()
t heImageView.image = UIImage(systemName: "arrow.uturn.
backward.square.fill")
t heImageView.translatesAutoresizingMaskIntoConstraints =
false
return theImageView
}()

327
Chapter 12 Book Details View Controller

Drawing Screen
The code defined in Table 12-13 draws the six blocks on the top, table view
for book details in the middle and the tab bar at the bottom of the screen.

Table 12-13. Drawing Top Six Blocks


//MARK: - Draw Screens
/*
T he function taskScreen will draw the book screen with all 6
functional block and the book details table view and the tab
bar
*/

func taskScreen(){
let screensize: CGRect = UIScreen.main.bounds
let screenHeight = screensize.height

l et fontTask = UIFont.systemFont(ofSize: 20, weight:


.bold)

let attributesMain: [NSAttributedString.Key: Any] = [


.font: fontTask
]

l et fontText = UIFont.systemFont(ofSize: 14, weight:


.bold)
let attributesText: [NSAttributedString.Key: Any] = [
.font: fontText
]

view.addSubview(bookLabel)
c ommonFunctions.setFieldLayout(mainField: bookLabel,
constraintField: view!, topAnchor: 20, leftAnchor: 0,
rightAnchor: 0, heightAnchor: 80)
(continued)

328
Chapter 12 Book Details View Controller

Table 12-13. (continued)

b ookLabel.attributedText = NSAttributedString(string:
book.bookname.capitalized, attributes: attributesMain)

view.addSubview(shareButton)
c ommonFunctions.setFieldLayout(mainField: shareButton,
constraintField: view!, topAnchor: 100, leftAnchor: 10,
rightAnchor: -270, heightAnchor: 80)
view.addSubview(shareImageView)
c ommonFunctions.setFieldLayout(mainField: shareImageView,
constraintField: shareButton, topAnchor: 5, leftAnchor:
40, rightAnchor: -40, heightAnchor: 30)
view.addSubview(shareTextLabel)
c ommonFunctions.setFieldLayout(mainField: shareTextLabel,
constraintField: shareButton, topAnchor: 40, leftAnchor:
2, rightAnchor: -2, heightAnchor: 30)
s hareTextLabel.attributedText = NSAttributedString(string:
"Share", attributes: attributesText)
view.addSubview(editButton)
c ommonFunctions.setFieldLayout(mainField: editButton,
constraintField: view!, topAnchor: 100, leftAnchor: 150,
rightAnchor: -140, heightAnchor: 80)
view.addSubview(editImageView)
c ommonFunctions.setFieldLayout(mainField: editImageView,
constraintField: editButton, topAnchor: 5, leftAnchor: 30,
rightAnchor: -30, heightAnchor: 30)
view.addSubview(editTextLabel)
c ommonFunctions.setFieldLayout(mainField: editTextLabel,
constraintField: editButton, topAnchor: 40, leftAnchor: 2,
rightAnchor: -2, heightAnchor: 30)
(continued)

329
Chapter 12 Book Details View Controller

Table 12-13. (continued)

e ditTextLabel.attributedText = NSAttributedString(string:
"Edit", attributes: attributesText)
s elf.editImageView.image = UIImage(systemName:
"checkmark")
view.addSubview(deleteButton)
c ommonFunctions.setFieldLayout(mainField: deleteButton,
constraintField: view!, topAnchor: 100, leftAnchor: 280,
rightAnchor: -10, heightAnchor: 80)
view.addSubview(deleteImageView)
c ommonFunctions.setFieldLayout(mainField: deleteImageView,
constraintField: deleteButton, topAnchor: 5, leftAnchor:
35, rightAnchor: -30, heightAnchor: 30)
view.addSubview(deleteTextLabel)
c ommonFunctions.setFieldLayout(mainField: deleteTextLabel,
constraintField: deleteButton, topAnchor: 40,
leftAnchor: 2, rightAnchor: -2, heightAnchor: 30)
d eleteTextLabel.attributedText =
NSAttributedString(string: "Delete", attributes:
attributesText)
view.addSubview(notesButton)
c ommonFunctions.setFieldLayout(mainField: notesButton,
constraintField: view!, topAnchor: 200, leftAnchor: 10,
rightAnchor: -270, heightAnchor: 80)
view.addSubview(notesImageView)
(continued)

330
Chapter 12 Book Details View Controller

Table 12-13. (continued)

c ommonFunctions.setFieldLayout(mainField: notesImageView,
constraintField: notesButton, topAnchor: 5, leftAnchor:
40, rightAnchor: -40, heightAnchor: 30)
view.addSubview(notesTextLabel)
c ommonFunctions.setFieldLayout(mainField: notesTextLabel,
constraintField: notesButton, topAnchor: 40,
leftAnchor: 2, rightAnchor: -2, heightAnchor: 30)
n otesTextLabel.attributedText = NSAttributedString(string:
"Book Notes", attributes: attributesText)
view.addSubview(reminderButton)
c ommonFunctions.setFieldLayout(mainField: reminderButton,
constraintField: view!, topAnchor: 200, leftAnchor: 150,
rightAnchor: -140, heightAnchor: 80)
view.addSubview(reminderImageView)
c ommonFunctions.setFieldLayout(mainField: reminderImage
View, constraintField: reminderButton, topAnchor: 5,
leftAnchor: 35, rightAnchor: -40, heightAnchor: 30)
view.addSubview(reminderTextLabel)
c ommonFunctions.setFieldLayout(mainField: reminderText
Label, constraintField: reminderButton, topAnchor: 40,
leftAnchor: 2, rightAnchor: -2, heightAnchor: 30)
r eminderTextLabel.attributedText = NSAttributedString
(string: "Reminder", attributes: attributesText)
view.addSubview(favButton)
c ommonFunctions.setFieldLayout(mainField: favButton,
constraintField: view!, topAnchor: 200, leftAnchor: 280,
rightAnchor: -10, heightAnchor: 80)
(continued)

331
Chapter 12 Book Details View Controller

Table 12-13. (continued)


view.addSubview(favImageView)
c ommonFunctions.setFieldLayout(mainField: favImageView,
constraintField: favButton, topAnchor: 5, leftAnchor: 35,
rightAnchor: -30, heightAnchor: 30)
view.addSubview(favTextLabel)
c ommonFunctions.setFieldLayout(mainField: favTextLabel,
constraintField: favButton, topAnchor: 40, leftAnchor: 2,
rightAnchor: -2, heightAnchor: 30)
f avTextLabel.attributedText = NSAttributedString(string:
"Mark Favorite", attributes: attributesText)

if(book.favorite == 1){
favButton.backgroundColor = .lightGray
favTextLabel.backgroundColor = .lightGray
}else{
favButton.backgroundColor = .white
favTextLabel.backgroundColor = .white
}
view.addSubview(bookTableView)
c ommonFunctions.setFieldLayout(mainField: bookTableView,
constraintField: view!, topAnchor: 300, leftAnchor: 5,
rightAnchor: -5, heightAnchor: screenHeight - 400)
view.addSubview(tabBar)
c ommonFunctions.setFieldLayout(mainField: tabBar,
constraintField: view!, topAnchor: screenHeight - 80,
leftAnchor: 0, rightAnchor: 0, heightAnchor: 60)
tabBar.unselectedItemTintColor = .blue
}

332
Chapter 12 Book Details View Controller

Displaying the Book Details


In this section, we will learn how to display the table view with the book
data (Book Name, Author Name, Genre, and Reading Status).
As we have learned before, we have created a variable bookDetails
(Swift is case sensitive) of our custom Struct type BookDetails, which will
hold all book details to be displayed in a table view. The Table View will
have the following functions:

Number of Sections
The table will have four sections, one each for Book Name, Author Name,
Genre, and Reading Status. Table 12-14 has the relevant code to let the
table know how many sections are there in the table.

Table 12-14. Table View – Number of Sections


/*
Each Field is treated as a section. The numberOfSections
function will let the table how many section to display
*/

func numberOfSections(in tableView: UITableView) -> Int {


return bookdetails.count
}

Number of Rows
Every section will only have one row. However, the genre can have more
than one genre (currently not Implemented). Table 12-15 has all the
relevant code.

333
Chapter 12 Book Details View Controller

Table 12-15. Table View – Number of Rows in Section


/*
numberOfRowsInSection will let the table know how many rows
are in each section.
*/

f unc tableView(_ tableView: UITableView, numberOfRows


InSection section: Int) -> Int {
return self.bookdetails[section].value.count
}

Display the Table


We need to note a few things while displaying the table:

• We are using a custom cell. The custom cell will display


the display field title, an icon to enhance readability
and the value (see next section for the definition of
custom cell).

• Four different icons will be displayed based on the title


field (Book name, Author name, Genre, and Reading
Status).

• Table 12-16 has all the relevant code to display


the values.

334
Chapter 12 Book Details View Controller

Table 12-16. Table View – Cell for Row At


/*
c ellForRowAt will display the records with an icon for each
type of field
*/

f unc tableView(_ tableView: UITableView, cellForRowAt


indexPath: IndexPath) -> UITableViewCell {
l et cell = tableView.dequeueReusableCell(withIdentifier:
"task", for: indexPath) as! IconTableViewCell
/ /cell.iconLabelButton.addTarget(self, action:
#selector(iconButtonAction), for: .touchUpInside)
cell.iconLabelButton.setTitleColor(.blue, for: .normal)
c ell.iconLabelButton.setTitle(bookdetails[indexPath.
section].value[indexPath.row], for: .normal)
cell.iconLabelButton.tag = indexPath.row
c ell.iconLabelButton.accessibilityLabel =
bookdetails[indexPath.section].label
c ell.iconLabelButton.accessibilityIdentifier =
String(indexPath.section)

switch bookdetails[indexPath.section].label{
case "Book Name":
c ell.leftImageView.image = UIImage(systemName: "book.
fill")
case "Author":
c ell.leftImageView.image = UIImage(systemName:
"person")
(continued)

335
Chapter 12 Book Details View Controller

Table 12-16. (continued)

case "Genre":
c ell.leftImageView.image = UIImage(systemName:
"pencil")
default:
c ell.leftImageView.image = UIImage(systemName:
"square.and.pencil")
}

return cell
}

Row Height
We are setting every row height to 40 pixels, as shown in Table 12-17.

Table 12-17. TableView – Height for Row At Index


/*
The height of the each row is set to 40 pixel
*/

f unc tableView(_ tableView: UITableView, heightForRowAt


indexPath: IndexPath) -> CGFloat {
return 40.0
}

336
Chapter 12 Book Details View Controller

Header View
The section header will have a custom label to display the section label
bold and with a large font size as shown in Table 12-18

Table 12-18. TableView – Header View


/*
E ach Section of the table view will have a title which will
be displayed in this section
*/

f unc tableView(_ tableView: UITableView,


viewForHeaderInSection section: Int) -> UIView? {
l et headerView = UIView.init(frame: CGRect.init(x: 0, y: 0,
width: tableView.frame.width, height: 50))
h eaderView.backgroundColor = UIColor.init(red: 245/255,
green: 245/255, blue: 245/255, alpha: 1)
let label = UILabel()
l abel.frame = CGRect.init(x: 15, y: 0, width: headerView.
frame.width-10, height: headerView.frame.height-10)
label.text = bookdetails[section].label
label.textColor = .black
label.font = UIFont.boldSystemFont(ofSize: 18)

headerView.addSubview(label)

return headerView
}

337
Chapter 12 Book Details View Controller

Header Height
We are setting the height of the header to 40 Pixels, as shown in
Table 12-19.

Table 12-19. TableView – Header Height


/*
The height of the each section is set to 40 pixel
*/

f unc tableView(_ tableView: UITableView,


heightForHeaderInSection section: Int) -> CGFloat {
return 40.0
}

Defining the Custom Cell


The Book Detail table view uses a custom cell to display the book record.
The cell definition has a custom label and an image which will provide
an icon notation to the displayed value. The cell in a table is a view and
we can customize the view to display data as we desire to. This class
will have the code required to draw every cell with the defined custom
layout. This custom cell is then attached to the Table cell type while
defining the table (bookTableView.register(IconTableViewCell.self,
forCellReuseIdentifier: "task"). It is referred to again in the table view
function cellForRowAt.

338
Chapter 12 Book Details View Controller

Follow the following steps to create the custom cell.

• Create a custom class called IconTableViewCell of type


UI Table View Cell, as shown in Table 12-20.

Table 12-20. UITableViewCell – Class Definition


class IconTableViewCell: UITableViewCell {

• Define a class level variable to get access to the


Common Function custom class. The method in
Common Function will be used to plot the objects in
the cell, as shown in Table 12-21.

Table 12-21. UITableViewCell – Class Level Variable Definition


/*
Variable to access the CommonFunctions class to draw the UI
Object in cell
*/
let commonFunctions = CommonFunctions()

• Define three UI Objects – a Button, an Image, and a


label. The Image will be contained within the button.
Although we do not want to take any action on the
click event. We are doing this so that in future we need
any event, we can easily use the default click event.
The Label is to display the value. The relevant code is
shown in Table 12-22.

339
Chapter 12 Book Details View Controller

Table 12-22. UITableViewCell – UI Object Definition


//MARK: - All Display View Items
/*
An UI Button to hold the image
*/

let iconButton:UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
b utton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 5,
bottom: 0, right: 0)
button.contentHorizontalAlignment = .left
button.tintColor = .black

button.clipsToBounds = true
button.isEnabled = false
return button
}()
/*
An image view to show the Book Field image
*/

let leftImageView: UIImageView = {


let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false

imageView.tintColor = .blue

return imageView
}()

/*
A label to show the book field data
*/
(continued)

340
Chapter 12 Book Details View Controller

Table 12-22. (continued)

let iconLabelButton:UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
b utton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 5,
bottom: 2, right: 0)
button.contentHorizontalAlignment = .left
button.contentVerticalAlignment = .center
button.setTitleColor(.black, for: .normal)
b utton.titleLabel?.font = UIFont(name: "Helvetica",
size: 14)
button.clipsToBounds = true

return button
}()

• Draw the cell Object. The SetSelected is the default


cell method which is called to draw the cell, as shown
in Table 12-23. This completed our custom class
definition.

341
Chapter 12 Book Details View Controller

Table 12-23. UITableViewCell – Draw Cell Object


/*
T his function is called every time the table cell is drawn.
The function draws the an Icon and put the Book Data next to
it.
*/

override func setSelected(_ selected: Bool, animated: Bool) {


super.setSelected(selected, animated: animated)

contentView.addSubview(iconButton)
c ommonFunctions.setFieldLayout(mainField: iconButton,
constraintField: contentView, topAnchor: 2, leftAnchor: 2,
rightAnchor: -345, heightAnchor: 20)

contentView.addSubview(leftImageView)
c ommonFunctions.setFieldLayout(mainField: leftImageView,
constraintField: contentView, topAnchor: 6, leftAnchor: 2,
rightAnchor: -360, heightAnchor: 20)

contentView.addSubview(iconLabelButton)
c ommonFunctions.setFieldLayout(mainField: iconLabelButton,
constraintField: contentView, topAnchor: 2, leftAnchor:
20, rightAnchor: 0, heightAnchor: 30)

Tab Bar Function


Return to the BookViewController and add the code as shown in
Table 12-24. We have a tab bar at the bottom of the screen with a button to
close the current screen and return to the main display view. The following
code dismisses the BooKViewController screen.

342
Chapter 12 Book Details View Controller

Table 12-24. Tab Bar Function


//MARK: - Tab Bar Function
/*

W hen the tab bar button is clicked the screen is dismissed


and the control will be returned to the main display view
*/

func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)


{
dismiss(animated: true, completion: nil)
}

Summary
This chapter was not introduced in the Book Tracker Basic App (Part I).
In this chapter, we learned how to display details content of a book (Book
Name, Author Name, Book Genre, Reading Status) and provide a list of
actions (Share the Book, Edit Book Details, Delete the Book, Take Book
Notes, Set Book Reminder, and mark it as Favorite).
The next six chapters are dedicated to each of the actions we have on
the Book Detail View Controller.

343
CHAPTER 13

Sharing Book
with Other Users
One of the functionalities iCloud offers is sharing content with other users.
In the CloudKit Overview section, a brief outline of the share concept was
discussed. In this section, we will learn the actual implementation, where
we will share a book with another user of the Book Tracker App. The App
had a screen dedicated for Shared books where all shared books by other
users are displayed, as shown in Figure 13-1.

© Shantanu Baruah and Shaurya Baruah 2023 345


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_13
Chapter 13 Sharing Book with Other Users

Figure 13-1. Screen Simulator Book Tracking App

Before we can see the books in the Shared Books, we need to first learn
how to share a book. Our Book Detail screen first option block is Share, as
shown in Figure 13-2. This section we will learn how to share a book. The
Shared Books Display we will learn in the next chapter.

346
Chapter 13 Sharing Book with Other Users

^ŚĂƌĞ ŽŽŬ KƉƟŽŶ

Figure 13-2. Screen Simulator Book Tracking App – Share


Book Option

Import CloudKit
Importing the CloudKit toolkit is required to get access to all native iCloud
functions, as shown in Table 13-1.

Table 13-1. Import Cloudkit


import CloudKit

347
Chapter 13 Sharing Book with Other Users

Share Button Click Event


While defining the shared button, we assigned a custom method called
“shareButtonAction” to the button Touch Up Inside event. The method
explained in Table 13-2 is executed when the touchupinside event on the
button is triggered by user button tap action.

Table 13-2. Share Button Click Event Action Method


/*
W hen the user clicks on the Share Button the following
function will be triggered. It will do the following:

- give the block a bouncing effect


- call the functions shareRecord to allow user to Share the
current book with other users and wait for its completion
*/

@objc func shareButtonAction(sender: UIButton){


animation(sender: sender)
self.shareRecord()
self.databaseFunctions.group.enter()
self.databaseFunctions.group.notify(queue: .main) {
}
}
/*
shareRecord function will do the folliwng
- Query the Book Repository for locating the book record
- Invoke a Share Record Window
- Send the message to the shared user(s)
*/
(continued)

348
Chapter 13 Sharing Book with Other Users

Table 13-2. (continued)

1. @objc func shareButtonAction(sender: UIButton){

Custom function attached to the Share Block button touchupinside event

2. animation(sender: sender)

animation will help to show a bounce effect. We will learn about it later.

3. self.shareRecord()

This method will help us to share the record. We will learn this method in the next
section.

4. self.databaseFunctions.group.enter()
5. self.databaseFunctions.group.notify(queue: .main) {

6. }

This is an await block for the share block to finish the work

7. }

Share Record Functions


This function will help share the book with other users. The function will
prompt a screen for sharing the book and it will offer multiple mediums
to do the same (e.g., iMessage, WhatsApp). The recipient user will get the
request on the medium chosen by the sender (for example iMessage).
The recipient user can then open the request, which will prompt the user
to either accept or reject the request. When the recipient user accepts the
record, the book record gets stored in a Shared Database, which both users
can view/modify. Any update done by any user will be visible to all shared
users. The relevant code is explained in Table 13-3.

349
Chapter 13 Sharing Book with Other Users

Table 13-3. BookShare Function


func shareRecord(){
DispatchQueue.main.async {
var share: CKShare? = nil
let privateDB = CKContainer.default().private
CloudDatabase
var pred = NSPredicate(value: true)
pred = NSPredicate(format: "bookname == %@ &&
authorname == %@", self.book.bookname, self.book.
authorname)

let query = CKQuery(recordType: "Book", predicate:


pred)
let operation = CKQueryOperation(query: query)
let ckRecordZoneID = CKRecordZone(zoneName:
"SharedZone")
operation.zoneID = ckRecordZoneID.zoneID
CKContainer.default().privateCloudDatabase.
add(operation)
var recordToShare:CKRecord?
operation.recordFetchedBlock = { record in
recordToShare = record
}

operation.queryCompletionBlock = { [unowned self]


(cursor, error) in
DispatchQueue.main.async {
if let error = error {
print(error)
}else {
(continued)

350
Chapter 13 Sharing Book with Other Users

Table 13-3. (continued)

share = CKShare(rootRecord:
recordToShare!)
share![CKShare.SystemFieldKey.title] =
"Sharing Book" as CKRecordValue?
share![CKShare.SystemFieldKey.
shareType] = "Book" as CKRecordValue
let shareController =
UICloudSharingController {
shareController,
preparationCompletionHandler in
let modRecordsOp = CKModifyRe
cordsOperation(recordsToSave:
[recordToShare!, share!],
recordIDsToDelete: nil)
modRecordsOp.modifyRecords
CompletionBlock = { records,
recordIDs, error in
if error != nil {
print("modifyRecordsOp
error:", error!)
}
preparationCompletionHandler(
share, CKContainer.default(),
error)
}
privateDB.add(modRecordsOp)
}
(continued)

351
Chapter 13 Sharing Book with Other Users

Table 13-3. (continued)

shareController.delegate = self
shareController.availablePermissions =
[.allowPrivate, .allowReadWrite]
shareController.
popoverPresentationController?.
sourceView = self.shareImageView
self.presentationController?.
presentedViewController.
present(shareController, animated:
true, completion: nil)
}
}
}
self.databaseFunctions.group.leave()
}
}

1. func shareRecord(){

Custom function called from Share Block button touch up inside event.

2. DispatchQueue.main.async {

We are creating a queue to help perform the share record execution to happen
asynchronously

3. var share: CKShare? = nil

We will define a local method variable called share of type CKShare. This will show
the Record Share Screen to the user.
4. let privateDB = CKContainer.default().privateCloudDatabase
5. var pred = NSPredicate(value: true)

(continued)

352
Chapter 13 Sharing Book with Other Users

Table 13-3. (continued)

6. print(self.book.bookname!)
7. print(self.book.authorname!)
8. p red = NSPredicate(format: "bookname == %@ && authorname
== %@", self.book.bookname, self.book.authorname)
9. let query = CKQuery(recordType: "Book", predicate: pred)
10. let operation = CKQueryOperation(query: query)
11. let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
12. operation.zoneID = ckRecordZoneID.zoneID
13. CKContainer.default().privateCloudDatabase.add(operation)
14. var recordToShare:CKRecord?
15. operation.recordFetchedBlock = { record in
16. recordToShare = record
17. }

The book we are going to share is currently in the user’s private Database. We are
using the preceding methods to get access to the database and search for the book
that needs to be shared. Once we query and locate the record, we will store thar
record in a CKRecord variable type called recordToShare

18. o peration.queryCompletionBlock = { [unowned self]


(cursor, error) in

As this is an asynchronous call, once the query is completed, it will return to this
block of code

19. DispatchQueue.main.async {
20. if let error = error {
21. print(error)

If there is an error in fetching the record, we will print it on the console.


(continued)

353
Chapter 13 Sharing Book with Other Users

Table 13-3. (continued)


22. }else {

If there is no error, then the following code will be executed.

23. share = CKShare(rootRecord: recordToShare!)

The preceding line of code is making the book record we queried to a Shared
Record type

24. s hare![CKShare.SystemFieldKey.title] = "Sharing Book" as


CKRecordValue?

This is a prompt which will be displayed on the shared popup screen. The title of the
screen – we are naming it “Sharing Book”

25. share![CKShare.SystemFieldKey.shareType] = "Book" as


CKRecordValue

Before sharing, we need to inform the iCloud database about the record type. Our
book is of type Book defined in the iCloud Database.

26. l et shareController = UICloudSharingController {


shareController,
27. preparationCompletionHandler in
28. l et modRecordsOp = CKModifyRecordsOperation(recordsToSave:
[recordToShare!, share!],
29. recordIDsToDelete: nil)

We are creating a Sharing Controller screen which will be shown to the user. Also
note, we need to mark this record in the Private Database as shared. When the user
accepts the request, it will be then stored in a shared database.

30.modRecordsOp.modifyRecordsCompletionBlock = { records,
recordIDs, error in
31. if error != nil {
(continued)

354
Chapter 13 Sharing Book with Other Users

Table 13-3. (continued)


32. print("modifyRecordsOp error:", error!)
33. }
34. p reparationCompletionHandler(share,
CKContainer.default(), error)
35. }
36. privateDB.add(modRecordsOp)
37. }

The preceding line of code commits the book record to the database

38. shareController.delegate = self


39. s hareController.availablePermissions = [.allowPrivate,
.allowReadWrite]
40. s hareController.popoverPresentationController?.sourceView
= self.shareImageView
41. s elf.presentationController?.presentedViewController.
present(shareController, animated: true, completion: nil)

Line 38–41 will display the share screen

42. }
43. }
44. }
45. self.databaseFunctions.group.leave()

Once the sharing is done, we will leave the queue and return the control to the
waiting block.
46. }
47. }

355
Chapter 13 Sharing Book with Other Users

Cloud Sharing Call Back Function


We need to implement two cloud sharing call back functions, as shown
in Table 13-4. The first one is to display an error if there is one, and the
second one is a call back to display a title on the screen. In our example, we
will display the book name as a title prompt to the user.

Table 13-4. Cloud Sharing Callback Functions


/*
If there is error in saving the Shared record the
following function is called, which prints the error on
the screen
*/

func cloudSharingController(_ csc:


UICloudSharingController, failedToSaveShareWithError
error: Error) {
print("failed to save: \(error.localizedDescription)")
}
/*
The following function returns the title that need to be
shown on the shared window popup

*/

func itemTitle(for csc: UICloudSharingController) ->


String? {
return "Sharing MyLibrary Book: \(self.book.bookname!)"
}

356
Chapter 13 Sharing Book with Other Users

Summary
Sharing the book with another Book Tracker App user is a cool feature. In
this chapter, we learned how to Share records securely amongst users. We
also learned the concepts of Private and shared iCloud databases in this
chapter.
In the next chapter, we will learn how to Edit the Details of a book.

357
CHAPTER 14

Edit Book
In the Book detail screen, the Edit Book block is the second block next to
the Share Book block. This block of functions helps in modifying the book
attributes (Book Name, Author Name, Genre, and Status). While defining the
Edit Book button, a custom function was assigned on the Touch Up Inside
Event called “completeButtonAction”. When the user taps on the block code
shown in Table 14-1 the Touch Up Inside function is invoked which opens
the Edit Book View Controller.

Calling the Edit View Controller


Table 14-1. Calling the Edit View Controller
/*
W hen the user clicks on the Edit Button the following
function will be triggered. It will do the following:

- give the block a bouncing effect


- Invoke the EditBookViewController view controller by
passing the book record to the controller. Also, please
note it set the delegate protocol, which after editing
will refresh the data on the current view controller
*/
(continued)

© Shantanu Baruah and Shaurya Baruah 2023 359


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_14
Chapter 14 Edit Book

Table 14-1. (continued)


@objc func completeButtonAction(sender: UIButton){
let viewController = EditBookViewController()
viewController.delegateBookViewRefresh = self
viewController.book = book
viewController.modalPresentationStyle = .currentContext
viewController.modalTransitionStyle = .crossDissolve
s elf.present(viewController, animated: true, completion:
nil)
}
Notice that we are passing the current Book to the Edit View Controller. This is to
pre-populate the Edit View Controller with the current book record values.

Edit View Controller


This View controller is exactly like the Add View Controller. The only
difference is it takes the Book details and pre-populates the screen with
the book details’ record values. Once saved, it calls the update Book
Functions instead of Add Book Function. In this section, we will only
learn about the additional changes required to update a book detail. If
you need to learn the complete implementation, please refer to the Add
Book Section code details. Add a new View Controller class and name it as
“EditViewController”. And add the code shown in Table 14-2.

360
Chapter 14 Edit Book

Class Level Variable


Table 14-2. Class Level Variables
// A variable of type Book to store the book data to be saved
to the repository
var book = DatabaseFunctions.Book()

// We also have a variable to keep the original book vales


intact as the editing will change it
var originalBook = DatabaseFunctions.Book()

// A Delegate to let the main display know the change post


editing is saved
var delegateBookViewRefresh:RefreshBooksViewProtocol? = nil
1. var book = DatabaseFunctions.Book()

This variable holds the book details which is passed by the calling function from the
Edit Book Block of Book Details View Controller. The update book details will also be
stored in this class variable

2. var originalBook = DatabaseFunctions.Book()

This variable holds the original book details

3. 
var delegateBookViewRefresh:RefreshBooksViewProtocol? = nil

This is the reference to delegate. Once the update is done and the screen is
dismissed, we need to update the Book Detail view with the updates. Refer to
Custom Delegation section to learn more.

Navigation Bar
There is no change in the Navigation Bar definition except for the Title of
the Bar, as shown in Table 14-3.

361
Chapter 14 Edit Book

Table 14-3. Navigation Bar Definition


private let addNavigationBar:UINavigationBar = {
let navigationBar = UINavigationBar()

navigationBar.translatesAutoresizingMaskIntoConstraints = false
n avigationBar.backgroundColor = UIColor.init(red: 171/255,
green: 189/255, blue: 217/255, alpha: 1)

let navItem = UINavigationItem(title: "Edit a Book")

l et resetSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 15, weight: .black)

l et resetImage = UIImage(systemName: "arrowshape.turn.


up.left.fill", withConfiguration: resetSymbolConfiguration)

l et editItem = UIBarButtonItem(image: resetImage,


landscapeImagePhone: resetImage, style: .plain, target:
nil, action: #selector(resetButtonAction))

l et saveSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 20, weight: .black)

l et saveImage = UIImage(systemName: "s.circle.fill",


withConfiguration: saveSymbolConfiguration)

l et saveItem = UIBarButtonItem(image: saveImage,


landscapeImagePhone: saveImage, style: .plain, target:
nil, action: #selector(saveButtonAction))
navItem.rightBarButtonItem = saveItem
navItem.leftBarButtonItem = editItem
navigationBar.setItems([navItem], animated: false)
return navigationBar
}()

362
Chapter 14 Edit Book

Setup
The setup is the same as defined in the Add View Controller. The only
change is assignment of the book fields with the values passed to the class
(Highlighted section). Note, we need to show user what they need to edit,
as shown in Table 14-4.

Table 14-4. Setup Function


func setup(){

genreTableView.delegate = self
genreTableView.dataSource = self

g enreTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "genre")
genreTableView.layer.borderWidth = 0
genreTableView.layer.borderColor = UIColor.white.cgColor
genreTableView.separatorStyle = .none

statusTableView.delegate = self
statusTableView.dataSource = self
s tatusTableView.register(UITableViewCell.self,
forCellReuseIdentifier: "status")
statusTableView.layer.borderWidth = 0
statusTableView.layer.borderColor = UIColor.white.cgColor
statusTableView.separatorStyle = .none
commonVariables.setGenres()
commonVariables.setStatuses()

bookTextField.text = book.bookname
authorTextField.text = book.authorname
genreTextField.text = book.genre
statusTextField.text = book.status
(continued)

363
Chapter 14 Edit Book

Table 14-4. (continued)


bookTextField.delegate = self
authorTextField.delegate = self

originalBook.bookname = book.bookname
originalBook.authorname = book.authorname
}

Save Book
The only change in the save book project is calling the Update Book
Database functions instead of Add Book (highlighted Section). Also, the
delegate calls to refresh the data on the Book Detail view, as shown in
Table 14-5.

Table 14-5. Save Book


/*
T his function updates the book values in the repository and
post saving to provides an alert that the book is saved.
*/

fileprivate func saveBook(){


databaseFunctions.group.enter()
d atabaseFunctions.updateBook(orginalBook: originalBook,
book:book)
databaseFunctions.group.notify(queue: .main) {
l et alertController = UIAlertController(title: "Save
Alert", message: "Book '\(self.book.bookname!)' is
Saved", preferredStyle: .alert)
(continued)

364
Chapter 14 Edit Book

Table 14-5. (continued)

// Create the actions


l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) { [self]
UIAlertAction in

s elf.delegateBookViewRefresh?.refreshData(book: self.
book)
self.dismiss(animated: true, completion: nil)
}

// Add the actions


alertController.addAction(okAction)
self.resetField()

// Present the controller


s elf.present(alertController, animated: true, completion:
nil)
}
}

Update Book Database Functions


This function is in our Common DatabaseFunctions Class. It queries
the Database first, and once found, it updates the record, as shown in
Table 14-6.

365
Chapter 14 Edit Book

Table 14-6. Update Book Record


/*
u pdate book updates the book with new values provided when
Edit Book Record is performed
*/
func updateBook(orginalBook: Book, book: Book){
l et pred = NSPredicate(format: "bookname == %@ &&
authorname == %@",orginalBook.bookname, orginalBook.
authorname)
let query = CKQuery(recordType: "Book", predicate: pred)
let operation = CKQueryOperation(query: query)
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
operation.zoneID = ckRecordZoneID.zoneID

l et sharedDatabase = CKContainer.default().
privateCloudDatabase
CKContainer.default().privateCloudDatabase.add(operation)
operation.recordFetchedBlock = { record in

record["bookname"] = book.bookname
record["authorname"] = book.authorname
record["genre"] = book.genre
record["status"] = book.status

s haredDatabase.save(record, completionHandler: {
(record, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print(error)
}
else {
self.syncQueue.async {
(continued)

366
Chapter 14 Edit Book

Table 14-6. (continued)

let book = Book()


b ook.bookname = record!.object(forKey:
"bookname")! as? String
b ook.authorname = record!.object(forKey:
"authorname")! as? String
b ook.genre = record!.object(forKey: "genre")!
as? String
b ook.status = record!.object(forKey: "status")!
as? String
self.book = book
self.group.leave()
}
}
}
})
}
}

Summary
In this chapter, we learned how to change the Book Details (Book Name
and Author Name) and select a different value for Genre and Book Reading
Status. We also learned how to update a book record in iCloud database.
In the next chapter, we will learn to delete a book from iCloud
repository.

367
CHAPTER 15

Book Delete
The third option on the Block Menu is deleting a Book. When a user clicks
on the Delete Block, “deleteButtonAction” custom function is invoked,
which provides an alert to the user asking for his/her confirmation to
delete the book. If users say “YES” the book is deleted.
Following is the progress made so far on Book Detail Screen:

• Share Book Record – Complete

• Edit Book Record – In Progress

• Delete Book Record

• Book Notes

• Setting Book Reminder

• Marking Book as Favorite

© Shantanu Baruah and Shaurya Baruah 2023 369


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_15
Chapter 15 Book Delete

Delete Block Button Action


/*When the user clicks on the Delete Button the following
function will be triggered. It will do the following:

- give the block a bouncing effect


- call the functions deleteBook to allow user to Share the
current book with other users and wait for its completion
*/

@objc func deleteButtonAction(sender: UIButton){


animation(sender: sender)
deleteBook()
}
1. @objc func deleteButtonAction(sender: UIButton){

The button Touch Up Inside Event defined for the delete block during
the view definition

2. animation(sender: sender)

animation will help to show a bounce effect. We will learn about it later.

3. deleteBook()

Custom function to Delete the book

4. }

370
Chapter 15 Book Delete

Custom Delete Book Function


/*
deleteBook function will prompt the user with the record to
be deleted Alert. If user says OK, it will delete the record
from the repository and return to the main screen
*/
func deleteBook(){
l et alertController = UIAlertController(title: "Delete
Book Action", message: "Are you sure you want to delete \
(self.book.bookname ?? "")?", preferredStyle: .alert)
// Create the actions
l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
s elf.databaseFunctions.deleteBook(bookname: self.book.
bookname, authorname: self.book.authorname)
self.databaseFunctions.group.enter()
s elf.commonFunctions.startSpinner(view: self.view!,
color: 1, spinner: self.spinner)
self.databaseFunctions.group.notify(queue: .main) {
s elf.commonFunctions.stopSpinner(spinner: self.
spinner)
self.delegateBooksViewRefresh?.refreshData()
self.dismiss(animated: true, completion: nil)
}
}

371
Chapter 15 Book Delete

l et cancelAction = UIAlertAction(title: "Cancel", style:


UIAlertAction.Style.cancel) {
UIAlertAction in
}
// Add the actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present the controller
s elf.present(alertController, animated: true, completion: nil)
}
The function shows an alert for book deletion, and when the user accepts to
delete, it calls the DatabaseFunctions class deleteBook Function. After deletion is
completed, the control returns to the waiting block and executes the following:

• Dismisses the current screen and returns to the main screen

• And refreshes the screen using the delegate protocol

Delete Book Database Function


In the DatabaseFunctions class, add the following deleteBook function.

// MARK: - Delete Book

/*
d eleteBook function deletes a book from the repository based
on the book name and author name
*/

func deleteBook(bookname: String, authorname: String){


var taskRecords = [CKRecord.ID]()
l et pred = NSPredicate(format: "bookname == %@ &&
authorname == %@", bookname, authorname)
let query = CKQuery(recordType: "Book", predicate: pred)

372
Chapter 15 Book Delete

let operation = CKQueryOperation(query: query)

let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")


operation.zoneID = ckRecordZoneID.zoneID
CKContainer.default().privateCloudDatabase.add(operation)
operation.recordFetchedBlock = { record in
taskRecords.append(record.recordID)
}
operation.queryCompletionBlock = { (cursor, error) in
DispatchQueue.main.async {
l et modifyRecordsOperation = CKModifyRecordsOperation.
init(recordsToSave: nil, recordIDsToDelete: taskRecords)
m odifyRecordsOperation.modifyRecordsCompletionBlock = {
records, recordIDs, error in DispatchQueue.main.async {
if let error = error {
print(error)
}else{
self.syncQueue.async {
self.deleteBookNotes(bookname: bookname)
}
}
}}
C KContainer.default().privateCloudDatabase.
add(modifyRecordsOperation)
}}
}
The function accepts two parameters: book name and author name. It queries the
user's private database for its existence. If found, it deletes the record, and calls
another function Delete Notes (we will learn about it in the next section) to delete all
related book notes and then returns to the calling function.

373
Chapter 15 Book Delete

Summary
In this chapter, we learned how to permanently delete a record from the
iCloud repository. In the next chapter, we will learn how to take additional
notes on the book.

374
CHAPTER 16

Book Notes
In the Book Detail View, the Book Notes allows users to take multiple notes
on the book. The notes are displayed on a new screen. Users can add and
delete notes from the popup screen. Figure 16-1 provides the screen layout
of the Book Notes screen.

Figure 16-1. Book Notes Layout Screen

© Shantanu Baruah and Shaurya Baruah 2023 375


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_16
Chapter 16 Book Notes

Following is the progress made so far on Book Detail Screen:

• Share Book Record - Complete

• Edit Book Record - In Progress

• Delete Book Record

• Book Notes

• Setting Book Reminder

 arking Book as FavoriteBook Notes Touch


M
Up Inside Event
The Book Notes button has a “notesButtonAction” function attached to
the Touch Up Inside event. The function defined in Table 16-1 is triggered
when user taps the Book Notes button.

Table 16-1. Notes UI Button Definition


//MARK: - Book Notes

/*
W hen the user clicks on the Book Notes Button the following
function will be triggered. It will do the following:

- give the block a bouncing effect


- Invoke the BookNotesViewController view controller by
passing the book record to the controller.
*/

@objc func notesButtonAction(sender: UIButton){


let viewController = BookNotesViewController()
viewController.book = book
viewController.modalPresentationStyle = .currentContext
(continued)
376
Chapter 16 Book Notes

Table 16-1. (continued)

viewController.modalTransitionStyle = .crossDissolve
s elf.present(viewController, animated: true, completion:
nil)
}
The function pops up the book Notes screen. It takes the book record as a reference
so that it knows which book it must store the notes for.

Book Notes View Controller


Create a View Controller with the name “BookNotesViewController”. This
view controller will inherit Table View Controller delegates to display Book
Notes and a Tab Bar delegate to dismiss the screen. Table 16-2 has the
relevant code.

Table 16-2. Class Inheritance


class BookNotesViewController: UIViewController,UITableViewDel
egate, UITableViewDataSource, UITabBarDelegate {

377
Chapter 16 Book Notes

Class Level Variables


Table 16-3 has the class level variables details.

Table 16-3. Class Variables


//MARK: - Class Variables
// the below three variables are used to access the common
class functions and variables
let commonFunctions = CommonFunctions()
let commonVariables = CommonVariables()
let databaseFunctions = DatabaseFunctions()
// A variable of type Book to store the book data to be
saved to the repository
var book = DatabaseFunctions.Book()
// A variable of type Booknote to store the book notes to be
saved to the repository. Please note, every book note is
a record.
var booknotes = [DatabaseFunctions.Booknote]()
Variables defined for

• provide access to Common Functions, Variables and Database classes


• Book Class to capture the book record

• 
Book Notes variable of type Booknote (defined in Database function class –
see below)

Book Notes Variable


Open the DataBaseFunctions Class and add the lines of code as shown in
Table 16-4 at class level.

378
Chapter 16 Book Notes

Table 16-4. Class Definition


/*
The Class Booknote mimics the Booknote table we have created
in our iCloud Repository.
*/
class Booknote{
var bookname:String!
var notes:String!
var authorname:String!
var bookid:CKRecord.ID!
var recordID: CKRecord.ID!
}

/*
b ooknotes is an array variable of type Booknote. booknote is
a variable of type Booknote
*/
var booknotes = [Booknote]()
var booknote = Booknote()
Booknote Class has notes as a String variable to store notes. It also has reference
to the Book and the author’s name and the unique Record id of the book for which
the note is stored.
The booknotes is a local array defined to store all booknotes(for query purposes)
and a booknote variable to save the current book note.

379
Chapter 16 Book Notes

Class Level UI Objects


We will need the following UI Objects for this view controller. The code is
shown in Table 16-5.
• A Text View to accept user inputs on notes

• A large popup view to cover the background screen

• A popup view to display the book notes popup display

• A Navigation bar to save and exit the popup screen

• A Tab Bar to exit the Book Notes Screen

• A Table view to display the book notes. The first row


will offer an option to add a Book Note

Table 16-5. UI Objects


//MARK: - UI Object

/*
Following UI Object will be used to draw the screen:
- a Text View to take free flow text as notes
- a largepopup to cover the Book Notes screen
- a popup which will show the Text view and a navigation bar
to save the book note when Add notes (the first row of the
Book Note table) option is clicked
- A Navigation bar with two buttons - one to close the popup
without saving and the other button to save and close the
book note
- A Tab bar with a button to close the screen
- A Book Notes table view to show the avaiable book notes
*/
(continued)

380
Chapter 16 Book Notes

Table 16-5. (continued)

private let bookTextView:UITextView = {


let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.backgroundColor = .white
textView.textColor = .black

return textView
}()
private let largerPopUpView:UIView = {
let uiView = UIView()
uiView.translatesAutoresizingMaskIntoConstraints = false
uiView.backgroundColor = .white
uiView.layer.borderColor = UIColor.black.cgColor
uiView.layer.borderWidth = 1
uiView.layer.cornerRadius = 5
return uiView
}()

private let popUpView:UIView = {


let uiView = UIView()
uiView.translatesAutoresizingMaskIntoConstraints = false
uiView.backgroundColor = .white
uiView.layer.borderColor = UIColor.black.cgColor
uiView.layer.borderWidth = 1
uiView.layer.cornerRadius = 5
return uiView
}()
(continued)

381
Chapter 16 Book Notes

Table 16-5. (continued)

private let createNotesNavigationBar:UINavigationBar = {


let navigationBar = UINavigationBar()
n avigationBar.translatesAutoresizingMaskIntoConstraints =
false
n avigationBar.backgroundColor = UIColor.init(red: 122/255,
green: 73/255, blue: 73/255, alpha: 1)

let navItem = UINavigationItem(title: "Add Note")

l et cancelSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 25, weight: .black)
l et cancelImage = UIImage(systemName: "arrowshape.turn.
up.left.fill", withConfiguration: cancelSymbolConfiguration)
l et cancelItem = UIBarButtonItem(image: cancelImage,
landscapeImagePhone: cancelImage, style: .plain, target:
nil, action: #selector(notesCancelButtonAction))
navItem.leftBarButtonItem = cancelItem

l et saveSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 25, weight: .black)
l et saveImage = UIImage(systemName: "s.circle.fill",
withConfiguration: saveSymbolConfiguration)
l et saveItem = UIBarButtonItem(image: saveImage,
landscapeImagePhone: saveImage, style: .plain, target: nil,
action: #selector(notesSaveActionButton))
navItem.rightBarButtonItem = saveItem
navigationBar.setItems([navItem], animated: false)

return navigationBar
}()
(continued)

382
Chapter 16 Book Notes

Table 16-5. (continued)

private let tabBar:UITabBar = {


let tabbar = UITabBar()
tabbar.translatesAutoresizingMaskIntoConstraints = false
t abbar.backgroundColor = UIColor.init(red: 171/255, green:
189/255, blue: 217/255, alpha: 1)
l et saveSymbolConfiguration = UIImage.
SymbolConfiguration(pointSize: 24, weight: .black)
l et saveImage = UIImage(systemName: "s.circle.fill",
withConfiguration: saveSymbolConfiguration)
l et save = UITabBarItem(title: "Save and Exit", image:
saveImage, selectedImage: saveImage)
save.tag = 0
tabbar.setItems([save], animated: false)
return tabbar
}()

//MARK: - Table UI Object

private let bookNotesTableView:UITableView = {


let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = .white
tableView.isScrollEnabled = true
return tableView
}()

Initial Loading
We are going to use the default viewDidLoad function to set up the view
Controller and a function bookNotesQuery to query the Booknote table
to display all available book notes for the book. Table 16-6 has the relevant
code details.
383
Chapter 16 Book Notes

Table 16-6. Lifecycle and Setup Functions


//MARK: - Lifecycle Events

/*
L everaging the default viewDidLoad methos to initiate
a custom function setup to perform initial setup and a
bookNotesQuery function to query all the available book
notes for the book
*/

override func viewDidLoad() {


super.viewDidLoad()
setup()
bookNotesQuery()
}

/*
The setup function does the following:
- set the background color of the view to white
- set the tab bar delegate
- set the book notes table view. The book note table view
uses a custom cell NotesTableViewCell
*/

func setup(){
view.backgroundColor = .white

tabBar.delegate = self

bookNotesTableView.delegate = self
bookNotesTableView.dataSource = self
b ookNotesTableView.register(NotesTableViewCell.self,
forCellReuseIdentifier: "note")
(continued)

384
Chapter 16 Book Notes

Table 16-6. (continued)

bookNotesTableView.layer.borderWidth = 0
b ookNotesTableView.layer.borderColor = UIColor.white.
cgColor
bookNotesTableView.separatorStyle = .none
}

/*
T he bookNotesQuery queries the repository for all available
booknotes for the current book. Once the query completes it
stores all the booknotes in a local variable "booknotes" and
draws the screen

*/
fileprivate func bookNotesQuery(){
self.databaseFunctions.booknotes.removeAll()
databaseFunctions.group.enter()
databaseFunctions.bookNotesQuery(book: book)
databaseFunctions.group.notify(queue: .main) {
self.booknotes = self.databaseFunctions.booknotes
self.drawScreen()
}

}
The Setup function sets the background color to white. Set delegates for Tab
Bar and the Book Notes Table View. Please note we are using a custom cell
NotesTableViewCell for displaying the notes.
The query function queries the Book Notes Table and once the query is completed,
it stores all book notes in a local booknotes variable of type Booknote class and
draws the screen with the UI Object.

385
Chapter 16 Book Notes

Custom Table View Cell for Book Notes


Notes Table View Cell has a notes button to display the notes. It is designed
to display the notes with a particular font, size, and style. Create a new
class named “NotesTableViewCell” of type UI Table View Cell and add the
code shown in Table 16-7.

Table 16-7. Custom Cell Definition


/*
This is a Custom cell class for displaying the Book Notes on
the screen.
*/

import UIKit

class NotesTableViewCell: UITableViewCell {

/*
V ariable to access the CommonFunctions class to draw the UI
Object in cell
*/

let commonFunctions = CommonFunctions()

//MARK: - All Display View Items

/*
An UI Button defined which will be drawn in the cell
*/

let notesLabelButton:UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
b utton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 5,
bottom: 2, right: 0)
(continued)
386
Chapter 16 Book Notes

Table 16-7. (continued)

button.contentHorizontalAlignment = .left
button.contentVerticalAlignment = .center
button.setTitleColor(.black, for: .normal)
b utton.titleLabel?.font = UIFont(name: "Helvetica",
size: 14)
button.clipsToBounds = true
button.layer.borderColor = UIColor.lightGray.cgColor
button.layer.cornerRadius = 5
button.layer.borderWidth = 1
button.titleLabel?.numberOfLines = 4

return button
}()

override func awakeFromNib() {


super.awakeFromNib()
}

/*
T his function is called every time the table cell is drawn.
The function draws the custom UI Button in the cell
*/

override func setSelected(_ selected: Bool, animated: Bool) {


super.setSelected(selected, animated: animated)
contentView.addSubview(notesLabelButton)
c ommonFunctions.setFieldLayout(mainField:
notesLabelButton, constraintField: contentView, topAnchor:
2, leftAnchor: 5, rightAnchor: -5, heightAnchor: 80)
}

387
Chapter 16 Book Notes

Draw the Notes Screen


Reopen “BookNotesViewController” class and add the code shown in
Table 16-8. The notes screen has a table view to add/display notes and a
tab bar to dismiss the notes screen and return to the book detail view.

Table 16-8. Draw Book Notes Display Screen


//MARK: - Draw Screen

/*
T his function draws the Book Notes screen. If Book notes are
avaiable it will display the same. The First row is always
Add Notes
*/

func drawScreen(){
let screensize: CGRect = UIScreen.main.bounds
let screenHeight = screensize.height

view.addSubview(bookNotesTableView)
c ommonFunctions.setFieldLayout(mainField:
bookNotesTableView, constraintField: view!, topAnchor:
50, leftAnchor: 5, rightAnchor: -5, heightAnchor:
screenHeight - 100)

view.addSubview(tabBar)
c ommonFunctions.setFieldLayout(mainField: tabBar,
constraintField: view!, topAnchor: screenHeight - 80,
leftAnchor: 0, rightAnchor: 0, heightAnchor: 60)
tabBar.unselectedItemTintColor = .blue
}
(continued)

388
Chapter 16 Book Notes

Table 16-8. (continued)

Figure 16-2. Book Notes Display Screen

Table View Function


To display the Book Notes, we will use a table view. We will use the
following Table functions:

• Number of Rows - Total rows we have in book notes


database for the current book.

• Display the Book Notes – This function will display


all the book notes, the first in the table allows user to
add a note.

389
Chapter 16 Book Notes

• The height of the Table – we have set it to 85 pixels as


the notes can have multiple lines.

• Header Section – To display the book name as a label.

• The Height of the header – set to default 40 pixels.

• Trailing Swap function – This will display a delete


option for the note upon the right swap on an
individual note.

• Table 16-9 has all the Table-related function code for


displaying the book notes of the selected book.

Table 16-9. Tableview Code for Displaying Book Notes


//MARK: - Table Functions

/*
numberOfRowsInSection will let the table know how many rows
are in each section.
*/
f unc tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return self.booknotes.count
}

/*
c ellForRowAt will display the records. The First row is Add
Notes option and it gets a Blue text color to make it stand out.
*/

f unc tableView(_ tableView: UITableView, cellForRowAt


indexPath: IndexPath) -> UITableViewCell {
(continued)

390
Chapter 16 Book Notes

Table 16-9. (continued)

l et cell = tableView.dequeueReusableCell(withIdentifier:
"note", for: indexPath) as! NotesTableViewCell
c ell.notesLabelButton.addTarget(self, action:
#selector(iconButtonAction), for: .touchUpInside)

if(indexPath.row == 0){
cell.notesLabelButton.setTitleColor(.blue, for: .normal)
cell.notesLabelButton.layer.borderWidth = 0
}else{
c ell.notesLabelButton.setTitleColor(.black, for:
.normal)
cell.notesLabelButton.layer.borderWidth = 1
}

c ell.notesLabelButton.setTitle(self.booknotes[indexPath.
row].notes, for: .normal)
cell.notesLabelButton.tag = indexPath.row
c ell.notesLabelButton.accessibilityIdentifier =
String(indexPath.section)

return cell

}
/*
The height of the each row is set to 85 pixel
*/

f unc tableView(_ tableView: UITableView, heightForRowAt


indexPath: IndexPath) -> CGFloat {
return 85.0
}
/*
(continued)

391
Chapter 16 Book Notes

Table 16-9. (continued)

T he viewForHeaderInSection will show the Header with the


Title "Notes for the Book: <Book Name> "
*/
f unc tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
l et headerView = UIView.init(frame: CGRect.init(x: 0, y: 0,
width: tableView.frame.width, height: 50))
h eaderView.backgroundColor = UIColor.init(red: 245/255,
green: 245/255, blue: 245/255, alpha: 1)
let label = UILabel()
l abel.frame = CGRect.init(x: 15, y: 0, width: headerView.
frame.width-10, height: headerView.frame.height-10)
label.text = "Notes for the Book: " + self.book.bookname
label.textColor = .black
label.font = UIFont.boldSystemFont(ofSize: 16)

headerView.addSubview(label)

return headerView
}

/*
T he height of the each header section is set to the default
40 pixel
*/

f unc tableView(_ tableView: UITableView,


heightForHeaderInSection section: Int) -> CGFloat {
return 40.0
}
(continued)

392
Chapter 16 Book Notes

Table 16-9. (continued)

/*
T he trailingSwipeActionsConfigurationForRowAt will display
a Trash icon upon left swipe, which will allow to delete an
individual book note
*/
f unc tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath:
IndexPath) -> UISwipeActionsConfiguration? {

l et deleteAction = UIContextualAction(style: .destructive,


title: "Delete", handler: { (action, view, success) in

l et alertController = UIAlertController(title: "Delete


Alert", message: "Are you Sure You want to Delete?",
preferredStyle: .alert)
// Create the actions
l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
self.databaseFunctions.group.enter()

s elf.databaseFunctions.deleteBookNote(notetoDelete:
self.booknotes[indexPath.row].notes,bookname: self.
booknotes[indexPath.row].bookname)
self.databaseFunctions.group.notify(queue: .main) {
self.booknotes.remove(at: indexPath.row)
self.bookNotesTableView.reloadData()
}
}
l et cancelAction = UIAlertAction(title: "Cancel",
style: UIAlertAction.Style.default) {
UIAlertAction in
(continued)
393
Chapter 16 Book Notes

Table 16-9. (continued)


self.bookNotesTableView.reloadData()
}
// Add the actions
alertController.addAction(cancelAction)
alertController.addAction(okAction)
// Present the controller
s elf.present(alertController, animated: true,
completion: nil)

})

deleteAction.backgroundColor = .red
deleteAction.image = UIImage(systemName: "trash")
r eturn UISwipeActionsConfiguration(actions:
[deleteAction])

}
This part we have learned before. If you need to revisit this section, please refer to
previous table view definitions.
Please note the first row of the Book Notes will be displayed as an “Add Note”
button. This will provide users with an option to add a note to the Book. To enable
the “Add Note” to popup a view to add a note, we are adding a method on the Touch
Up Inside event called “iconButtonAction”. Refer the preceding lines of code for
the implementation details.

Table View Trailing Swipe Control


Code in Table 16-10 implements the Trailing Swipe on Table Cell to show
the delete note option.

394
Chapter 16 Book Notes

Table 16-10. Tableview Trailing Swipe – Book Notes Delete


/*
T he trailingSwipeActionsConfigurationForRowAt will display
a Trash icon upon left swipe, which will allow to delete an
individual book note
*/

f unc tableView(_ tableView: UITableView,


trailingSwipeActionsConfigurationForRowAt indexPath:
IndexPath) -> UISwipeActionsConfiguration? {

l et deleteAction = UIContextualAction(style:
.destructive, title: "Delete", handler: { (action, view,
success) in

l et alertController = UIAlertController(title: "Delete


Alert", message: "Are you Sure You want to Delete?",
preferredStyle: .alert)
// Create the actions
l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
self.databaseFunctions.group.enter()
s elf.databaseFunctions.deleteBookNote(notetoDelete:
self.booknotes[indexPath.row].notes,bookname: self.
booknotes[indexPath.row].bookname)
self.databaseFunctions.group.notify(queue: .main) {
self.booknotes.remove(at: indexPath.row)
self.bookNotesTableView.reloadData()
}
}
(continued)

395
Chapter 16 Book Notes

Table 16-10. (continued)

l et cancelAction = UIAlertAction(title: "Cancel",


style: UIAlertAction.Style.default) {

UIAlertAction in
self.bookNotesTableView.reloadData()
}

// Add the actions


alertController.addAction(cancelAction)
alertController.addAction(okAction)

// Present the controller


s elf.present(alertController, animated: true,
completion: nil)

})
deleteAction.backgroundColor = .red
deleteAction.image = UIImage(systemName: "trash")
r eturn UISwipeActionsConfiguration(actions:
[deleteAction])

1. 
func tableView(_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath:
IndexPath) -> UISwipeActionsConfiguration? {

This is the system-defined table view function that allows swipe gestures on table
cells. Upon swipe, the table view provides option(s) for users to act. In this case, we
will show a delete note option.
(continued)

396
Chapter 16 Book Notes

Table 16-10. (continued)

2. 
let deleteAction = UIContextualAction(style: .destructive,
title: "Delete", handler: { (action, view, success) in

For our Book Note swipe, we are defining a delete action – giving it a title and a
style (destructive means if we swipe completely the action will be performed)

3. l et alertController = UIAlertController(title: "Delete


Alert", message: "Are you Sure You want to Delete?",
preferredStyle: .alert)

4. // Create the actions

5. l et okAction = UIAlertAction(title: "OK", style:


UIAlertAction.Style.default) {

6. UIAlertAction in

7. 
self.databaseFunctions.group.enter()

8. 
self.databaseFunctions.deleteBookNote(notetoDelete:
self.booknotes[indexPath.row].notes,bookname: self.
booknotes[indexPath.row].bookname)

9. self.databaseFunctions.group.notify(queue: .main) {

10. self.booknotes.remove(at: indexPath.row)

11. self.bookNotesTableView.reloadData()

When the user performs the delete action by swiping on a book note, an alert is
shown to confirm deletion, and if user confirms to the action a DatabaseFunctions
method deleteBookNote is called to perform the delete note action (details on the
method is provided in the next section)

12. }

13. }
(continued)

397
Chapter 16 Book Notes

Table 16-10. (continued)

14. let cancelAction = UIAlertAction(title: "Cancel", style:


UIAlertAction.Style.default) {

15. UIAlertAction in
16. self.bookNotesTableView.reloadData()

If user decided not to delete, it refreshes the table and closes the alert.

17. }

18. // Add the actions

19. alertController.addAction(cancelAction)

20. alertController.addAction(okAction)

21. // Present the controller

22. self.present(alertController, animated: true, completion:


nil)

Adding the OK and CANCEL buttons to the alert Controller. The alert Controller is
then presented using the system-defined present method.

23. })

24. deleteAction.backgroundColor = .red

25. deleteAction.image = UIImage(systemName: "trash")

26. return UISwipeActionsConfiguration(actions: [deleteAction])


Upon swipe we can show the option as text or icon – in our code we are showing
the option as an icon – we are using the system-defined Trash Icon with a red
background color
27. }

398
Chapter 16 Book Notes

Delete Notes
This is a database function method used for deleting the note. Add the
method shown in Table 16-11 to the DatabaseFunctions class.

Table 16-11. Delete Book Note Code


// MARK: - Delete Book Notes
/*
deleteBookNotes function deletes all booknotes of a book
*/

func deleteBookNotes(bookname:String){
var taskRecords = [CKRecord.ID]()
let pred = NSPredicate(format: "bookname = %@",bookname)
l et query = CKQuery(recordType: "Booknotes", predicate:
pred)
let operation = CKQueryOperation(query: query)
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
operation.zoneID = ckRecordZoneID.zoneID

CKContainer.default().privateCloudDatabase.add(operation)
operation.recordFetchedBlock = { record in
taskRecords.append(record.recordID)
}
operation.queryCompletionBlock = { (cursor, error) in
DispatchQueue.main.async {
l et modifyRecordsOperation = CKModifyRecordsOperation.
init(recordsToSave: nil, recordIDsToDelete: taskRecords)

m odifyRecordsOperation.modifyRecordsCompletionBlock = {
records, recordIDs, error in DispatchQueue.main.async {
(continued)

399
Chapter 16 Book Notes

Table 16-11. (continued)

if let error = error {


print(error)
}else{
self.syncQueue.async {
self.group.leave()
}
}
}}

C KContainer.default().privateCloudDatabase.
add(modifyRecordsOperation)
}}
}
We have learned how to delete a record from an iCloud table before. Please refer to
the previous section for the same. Please note the table is Booknotes (which stores
all book notes) and we are only deleting the book note on which delete swipe action
was performed.

Adding Notes
When user clicks the “Add Note” option, the event shown in Table 16-12 is
triggered.

400
Chapter 16 Book Notes
Table 16-12. Add Note Custom Function
/*
When Add Notes is clicked (the first row) it will will draw
the Book Notes Input Screeen
*/

@objc func iconButtonAction(sender: UIButton){


if(sender.tag == 0){
drawAddNotes()
}
}
In the uitableview CellforRowAt function for the BooknotesTableView, we stored
the current row number to the button Tag variable. This way when the user clicks
on the “Add Note” Button the button tag will always have the value 0. This is
because “Add Note” will always be the first option displayed on the table. The
iconButtonAction is called when the user clicks on the “Add Note” button. In the
body of the method, we are checking if the button tag value is 0 (first row), and if it
is 0 then we execute “drawAddNotes” to display the Add Notes popup.

401
Chapter 16 Book Notes

Draw the Add Notes Popup


The Code in Table 16-13 will display the Book Note popup screen.

Table 16-13. Add Note Popup Screen


/*
W hen Add Notes option is selected the Book Notes input
screen is drawn using the drawAddNotes function
*/

private func drawAddNotes(){

let screensize: CGRect = UIScreen.main.bounds


let screenHeight = screensize.height

view.addSubview(largerPopUpView)
c ommonFunctions.setFieldLayout(mainField: largerPopUpView,
constraintField: view!, topAnchor: 0, leftAnchor: 0,
rightAnchor: 0, heightAnchor: screenHeight)

l argerPopUpView.backgroundColor = UIColor(displayP3Red:
50, green: 50, blue: 50, alpha: 0.7)

largerPopUpView.addSubview(popUpView)
c ommonFunctions.setFieldLayout(mainField: popUpView,
constraintField: view!, topAnchor: 100, leftAnchor: 20,
rightAnchor: -20, heightAnchor: 350)

p opUpView.layer.shadowPath = UIBezierPath(roundedRect:
CGRect(x: -10, y: -10, width: 370, height: 370),
cornerRadius: 12).cgPath
popUpView.layer.shadowRadius = 2
popUpView.layer.shadowOffset = .zero
popUpView.layer.shadowOpacity = 0.8
(continued)

402
Chapter 16 Book Notes

Table 16-13. (continued)


popUpView.layer.shadowColor = UIColor.gray.cgColor

popUpView.layer.shouldRasterize = true
view.addSubview(createNotesNavigationBar)
c ommonFunctions.setFieldLayout(mainField:
createNotesNavigationBar, constraintField: popUpView,
topAnchor: 0, leftAnchor: 0, rightAnchor: 0,
heightAnchor: 20)

view.addSubview(bookTextView)
c ommonFunctions.setFieldLayout(mainField: bookTextView,
constraintField: createNotesNavigationBar, topAnchor: 50,
leftAnchor: 2, rightAnchor: -2, heightAnchor: 290)
bookTextView.becomeFirstResponder()
}
The screen will have a navigation bar and a text view. Navigation bar provides two
buttons: one to save the book note, and the other button will allow the user to close
the popup view. The Text view will be used to take book notes input from the user.

403
Chapter 16 Book Notes

Figure 16-3. Book Notes Add Notes Screen

Add Notes Navigation Bar Function


There are two buttons on the Add Notes Navigation Bar: one to Save
the Note and other to close the popup screen. Both these methods are
attached to the buttons in the Navigation Bar UI Object definition. The
relevant code is in Table 16-14.

404
Chapter 16 Book Notes

Table 16-14. Tab Bar Button Functions


/*
T he Navigation bar cancel button will remove the Add book
notes screen
*/
@objc func notesCancelButtonAction(sender: UIButton){
self.bookTextView.text = ""
removeAddNotesPopUpConstraints()
}

/*
The navigation bar save button to save the book note
*/
@objc func notesSaveActionButton(sender: UIButton){
saveNotes()
}
When the cancel button is selected, we remove all UI Objects from the popup
screen. As the book note can be displayed again, it is a good practice to remove all
UI Objects to avoid any constraint conflicts while drawing the UI Objects.
If Save action is selected, we will call the custom Save Notes method to save the
note to iCloud repository.

Remove Constraints
The code in Table 16-15 provides the detail on removing the Book Notes
screen when users close the popup book notes screen.

405
Chapter 16 Book Notes

Table 16-15. Remove Constraints


/*
R emove all Add book notes screen object when user closes the
window
*/

func removeAddNotesPopUpConstraints(){
l argerPopUpView.removeConstraints(largerPopUpView.
constraints)
largerPopUpView.removeFromSuperview()

popUpView.removeConstraints(popUpView.constraints)
popUpView.removeFromSuperview()

c reateNotesNavigationBar.removeConstraints(createNotesNavi
gationBar.constraints)
createNotesNavigationBar.removeFromSuperview()

bookTextView.removeConstraints(bookTextView.constraints)
bookTextView.removeFromSuperview()
}
Function to remove all the popup view UI Objects so that we can revert to the main
display.

Save Book Method


The code shown in Table 16-16 will invoke the save method to store the
book notes into the iCloud repository.

406
Chapter 16 Book Notes

Table 16-16. Save Book Notes Validation


/*
Saiving the book note to the repository
*/

func saveNotes(){
if(bookTextView.text == ""){
bookTextView.text = ""
l et alertController = UIAlertController(title: "Save
Alert", message: "No Notes to Save", preferredStyle:
.alert)
// Create the actions
l et okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
self.tabBarController?.selectedIndex = 0
}

// Add the actions


alertController.addAction(okAction)
// Present the controller
s elf.present(alertController, animated: true,
completion: nil)
bookTextView.becomeFirstResponder()
}
else{
self.databaseFunctions.booknote.bookname = book.bookname
s elf.databaseFunctions.booknote.authorname = book.
authorname
self.databaseFunctions.booknote.notes = bookTextView.text
self.databaseFunctions.booknote.bookid = book.recordID
self.booknotes.append(self.databaseFunctions.booknote)
(continued)
407
Chapter 16 Book Notes

Table 16-16. (continued)

self.databaseFunctions.group.enter()
self.databaseFunctions.saveBookNotes()
self.databaseFunctions.group.notify(queue: .main) {
self.removeAddNotesPopUpConstraints()
self.bookTextView.text = ""
self.bookNotesTableView.reloadData()
}
}
}
This function will save notes after doing all validation to the “Booknotes” Record
type in the iCloud database repository.

Database Save Book Method


Open the DatabaseFunctions class and add the method as shown in
Table 16-17.

Table 16-17. Save Book Notes in iCloud Repository


//MARK: - Save BooK Notes Record
/*
s aveBookNotes saves a booknotes for a book to the iCloud
repository
*/

func saveBookNotes(){
let ckRecordZoneID = CKRecordZone(zoneName: "SharedZone")
(continued)

408
Chapter 16 Book Notes

Table 16-17. (continued)


// Save the zone in the private database
let container = CKContainer.default()
let privateDB = container.privateCloudDatabase

privateDB.save(ckRecordZoneID){ zone, error in


if let error = error{
p rint("Zone creation error: \(String(describing:
error))")
}else{
print("Zone created: \(zone!)")
}
}
l et privateDatabase = CKContainer.default().
privateCloudDatabase
print(ckRecordZoneID.zoneID)
l et ckRecordID = CKRecord.ID(zoneID: ckRecordZoneID.
zoneID)
l et aRecord = CKRecord(recordType: "Booknotes", recordID:
ckRecordID)

aRecord["bookname"] = booknote.bookname
aRecord["authorname"] = booknote.authorname
aRecord["booknotes"] = booknote.notes

l et bookReference = CKRecord.Reference(recordID: booknote.


bookid, action: .deleteSelf)
aRecord["bookid"] = bookReference
aRecord.setParent(booknote.bookid)

p rivateDatabase.save(aRecord, completionHandler: {
(record, error) -> Void in
DispatchQueue.main.async {
(continued)
409
Chapter 16 Book Notes

Table 16-17. (continued)

if let error = error {


print(error)
}
else{
self.syncQueue.async {
self.group.leave()
}
}
}
})
}

Tab Bar Function


This function will be closed when the user clicks on the tab bar cancel
button. The body of the function has a dismiss function to close the Book
Notes View and return to the Book Detail View. The code in Table 16-18
provides the relevant details.

Table 16-18. Tab Bar Dismiss Function


//MARK: - Tab Bar Function

/*
D ismissing the Book Notes screen and return to the Book
Details screen
*/

func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem)


{
dismiss(animated: true, completion: nil)
}

410
Chapter 16 Book Notes

Summary
In this chapter, we learned how to set up parent child records in the iCloud
database. One book can have many notes. We learned how to Add notes,
delete notes, cascading delete all book notes when a book is deleted.
Next chapter, we will learn how to set up a Book Reminder.

411
CHAPTER 17

Book Reminder
Think of a scenario, you are putting together a list of books you want to
read on your next vacation, and now that you have a list of books you want
to read, you now want to set a reminder on the day of your vacation so that
you can carry the book and eventually read it.
When we set a reminder for a book, it will show on the iPhone
notification center as shown in Figure 17-2. Please note that for accessing
the notification, the user must give permission to the App to show the
notification as shown in Figure 17-1.
Following is the progress made so far on Book Detail Screen:

• Share Book Record - Complete

• Edit Book Record - In Progress

• Delete Book Record


• Book Notes

• Setting Book Reminder

• Marking Book as Favorite

© Shantanu Baruah and Shaurya Baruah 2023 413


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_17
Chapter 17 Book Reminder

Figure 17-1. Permission Request

Figure 17-2. Notification Screen

414
Chapter 17 Book Reminder

Reminder Action Button


The Reminder block has a Touch Up Inside event attached to the function
“reminderButtonAction”, as shown in Table 17-1.

Table 17-1. Reminder Button Action Function


/*
When the user clicks on the Share Button the following
function will be triggered. It will do the following:

- give the block a bouncing effect


- call the functions drawReminderPopupScreen which will
draw the Reminder screen to set Reminder for the book
*/

@objc func reminderButtonAction(sender: UIButton){


animation(sender: sender)
drawReminderPopupScreen()
}
1. @objc func reminderButtonAction(sender: UIButton){
This is the Touch Up Inside event for the Reminder Button.
2. animation(sender: sender)
Animation will help to show a bounce effect. We will learn about it later.
3. drawReminderPopupScreen()
This is to draw the popup screen for setting the Reminder
4. }

Draw Reminder Screen


This is a popup screen to allow the user to set the reminder. Following UI
Objects are drawn on the screen(refer to Table 17-2 for Code details):

415
Chapter 17 Book Reminder

• A large popup view to cover the Book Detail Screen

• A popup view to show the reminder UI objects

• A Date and time scroll for user to set a date and a time
when the reminder should appear for the book

• Two image views. The first one will save the reminder
to the iCloud repository and the second image view
will reset the reminder to a back date. Image view does
not have the touchUpInside event. To enable the click
event, we need to define it as a tap gesture event.

Table 17-2. Draw Reminder Popup Screen


/MARK: - Reminder Screen
/*
The Reminder Pop up screen is drawn using this function
*/

func drawReminderPopupScreen(){
let screensize: CGRect = UIScreen.main.bounds
let screenHeight = screensize.height
view.addSubview(largerPopUpView)
commonFunctions.setFieldLayout(mainField: largerPopUpView,
constraintField: view!, topAnchor: 0, leftAnchor: 0,
rightAnchor: 0, heightAnchor: screenHeight)

largerPopUpView.backgroundColor = UIColor(displayP3Red:
50, green: 50, blue: 50, alpha: 0.7)

largerPopUpView.addSubview(popUpView)
commonFunctions.setFieldLayout(mainField: popUpView,
constraintField: view!, topAnchor: 300, leftAnchor:
40, rightAnchor: -40, heightAnchor: 200)
(continued)

416
Chapter 17 Book Reminder

Table 17-2. (continued)

popUpView.layer.shadowPath = UIBezierPath(roundedRect:
CGRect(x: -10, y: -10, width: 330, height: 220),
cornerRadius: 12).cgPath
popUpView.layer.shadowRadius = 2
popUpView.layer.shadowOffset = .zero
popUpView.layer.shadowOpacity = 0.8
popUpView.layer.shadowColor = UIColor.gray.cgColor
popUpView.layer.shouldRasterize = true
popUpView.addSubview(reminderLabel)
commonFunctions.setFieldLayout(mainField:
reminderLabel, constraintField: popUpView, topAnchor:
10, leftAnchor: 2, rightAnchor: -2, heightAnchor: 40)
reminderLabel.text = "Set Reminder for Book \"\(book.
bookname.capitalized)\""
reminderLabel.font = UIFont.boldSystemFont(ofSize: 16)
popUpView.addSubview(reminderdatePicker)
commonFunctions.setFieldLayout(mainField:
reminderdatePicker, constraintField: popUpView, topAnchor:
80, leftAnchor: 0, rightAnchor: -40, heightAnchor: 40)
if(book.reminder != nil){
reminderdatePicker.date = book.reminder
}
popUpView.addSubview(reminderPopUpImageView)
commonFunctions.setFieldLayout(mainField: reminder
PopUpImageView, constraintField: popUpView, topAnchor:
140, leftAnchor: 100, rightAnchor: -160, heightAnchor: 40)
popUpView.addSubview(reminderResetImageView)
commonFunctions.setFieldLayout(mainField: reminderReset
ImageView, constraintField: popUpView, topAnchor: 140,
leftAnchor: 170, rightAnchor: -90, heightAnchor: 40)
}

417
Chapter 17 Book Reminder

Figure 17-3. Reminder Popup Screen

Save Reminder
As Image View doesn’t have any default event defined, we need to add a
tap gesture (see setup section to understand how to define tap gesture).
The function does the following:

• Calls a method to add notification to the


notification center.

• Update the Book class object reminder attribute with


the reminder date.

• Remove the pop screen UI Objects.

418
Chapter 17 Book Reminder

• Update the database with new reminder date.

• Table 17-3 has the relevant code details for the tap
gesture response on image view.

Table 17-3. Image View Tap Gestures


/*
reminderImageTapped is called when the user selects the
Set Reminder button. It calls the addNotification function
to add the book reminder to the iOS Notification center.
Also, set the reminder date to the book Reminder variable.
It removes the screen and calls the updateBookReminder to
update the reminder for the book in the iCloud repository
*/

@objc func reminderImageTapped(tapGestureRecognizer:


UITapGestureRecognizer){
self.addNotification(title: book.bookname,
reminderDate: reminderdatePicker.date)
book.reminder = reminderdatePicker.date
removeReminderContraints()
self.updateBookReminder()

Setup Reminder
This method sets the notification center with the book name that needs
to be reminded of the set date and time. Table 17-4 has the relevant code
details.

419
Chapter 17 Book Reminder

Table 17-4. Notification Setup Code


//MARK: - Notification Section
/*
The below function will add Notification to the iOS
notification console.
*/

func addNotification(title: String, reminderDate: Date) {


let center = UNUserNotificationCenter.current()
let addRequest = {
let content = UNMutableNotificationContent()
content.title = title.capitalized
content.subtitle = "\(title.capitalized) Book
Reminder"
content.sound = UNNotificationSound.default
let calendar = Calendar.current
var components = calendar.dateComponents([.year],
from: reminderDate)
let year = components.year
components = calendar.dateComponents([.month],
from: reminderDate)
let month = components.month
components = calendar.dateComponents([.day], from:
reminderDate)
let day = components.day
components = calendar.dateComponents([.hour],
from: reminderDate)
let hour = components.hour
components = calendar.dateComponents([.minute],
from: reminderDate)
let minute = components.minute
(continued)

420
Chapter 17 Book Reminder

Table 17-4. (continued)

var dateComponents = DateComponents()


dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
let trigger = UNCalendarNotificationTrigger(dateMa
tching: dateComponents, repeats: false)

let request = UNNotificationRequest(identifier


: UUID().uuidString, content: content, trigger:
trigger)
center.add(request)
}

center.getNotificationSettings { settings in
if settings.authorizationStatus == .authorized {
addRequest()
} else {
center.requestAuthorization(options: [.alert,
.badge, .sound]) { success, error in
if success {
addRequest()
} else {
print("Error is \(error?.localized
Description ?? "No error Code")")
}
}
}
}
}
(continued)
421
Chapter 17 Book Reminder

Table 17-4. (continued)


1. func addNotification(title: String, reminderDate: Date) {
This is a custom function called every time the reminder is set
2. let center = UNUserNotificationCenter.current()
We will get the User Notification center handle in a local variable.
3. let addRequest = {
4. let content = UNMutableNotificationContent()
5. content.title = title.capitalized
6. content.subtitle = "\(title.capitalized) Book Reminder"
7. content.sound = UNNotificationSound.default
8. let calendar = Calendar.current
9. var components = calendar.dateComponents([.year], from:
reminderDate)
10. let year = components.year
11. components = calendar.dateComponents([.month], from:
reminderDate)
12. let month = components.month
13. components = calendar.dateComponents([.day], from:
reminderDate)
14. let day = components.day
15. components = calendar.dateComponents([.hour], from:
reminderDate)
16. let hour = components.hour
17. components = calendar.dateComponents([.minute], from:
reminderDate)
18. let minute = components.minute
19. var dateComponents = DateComponents()
20. dateComponents.hour = hour
21. dateComponents.minute = minute
(continued)

422
Chapter 17 Book Reminder

Table 17-4. (continued)


22. dateComponents.year = year
23. dateComponents.month = month
24. dateComponents.day = day
25. let trigger = UNCalendarNotificationTrigger(dateMatching:
dateComponents, repeats: false)
26. //let trigger = UNTimeIntervalNotificationTrigger(timeInte
rval: 5, repeats: false)
27. let request = UNNotificationRequest(identifier: UUID().
uuidString, content: content, trigger: trigger)
28. center.add(request)
29. }
The preceding code creates a notification with the title as the book name for his
reminder needs to be set, informs the notification center on the date and time
when reminder needs to be displayed, creates the notification trigger, adds the
trigger to a notification request, and finally adds it to the notification center.
30. center.getNotificationSettings { settings in
31. if settings.authorizationStatus == .authorized {
32. addRequest()
33. } else {
34. center.requestAuthorization(options: [.alert, .badge,
.sound]) { success, error in
35. if success {
36. addRequest()
37. } else {
38. print("Error is \(error?.localizedDescription ?? "No
error Code")")
39. }
40. }
(continued)

423
Chapter 17 Book Reminder

Table 17-4. (continued)


41. }
Line 44–55 checks whether the user has access to the notification center. For
the first time, a notification display confirmation popup will be shown. If the
user agrees, it will inform the Notification Center that future notifications can be
displayed.
42. }
43. }

Reset Reminder
As Imageview does not have any event attached to it, we need to add a tap
gesture (see setup section to understand how to define tap gesture).
The function removed all UI Objects from the reminder popup screen,
as shown in Table 17-5.

Table 17-5. Set Reminder Tap Gesture


/*
reminderResetImageTapped will reset the date to a back
date and set the book reminder variabel to the same.
*/

@objc func reminderResetImageTapped(tapGestureRecognizer:


UITapGestureRecognizer){
var calendar = Calendar.current
calendar.timeZone = NSTimeZone.local
book.reminder = calendar.date(byAdding: .day, value:
-5, to: Date())
reminderdatePicker.date = calendar.date(byAdding:
.day, value: -5, to: Date())!
}
(continued)

424
Chapter 17 Book Reminder

Table 17-5. (continued)

1 . @objc func reminderResetImageTapped(tapGestureRecognizer:


UITapGestureRecognizer){
This method is defined for the ImageView Reset image
2. var calendar = Calendar.current
3. calendar.timeZone = NSTimeZone.local
4. book.reminder = calendar.date(byAdding: .day, value: -5,
to: Date())
5. reminderdatePicker.date = calendar.date(byAdding: .day,
value: -5, to: Date())!
Lines 2–5 we are getting access to the calendar. Getting the user's current local
time zone. The reminder is set to a back date, five days before the current date.
This way notification will be void.
6. }

Update Reminder
The method defined in Table 17-6 calls the DatabaseFunctions Update
Reminder Method to save the reminder data in Book Record Type. After
saving, it removes all UI Objects of the Reminder Popup screen.

425
Chapter 17 Book Reminder

Table 17-6. Reminder Update Function


/*
function to update the Book Repository with the updated
reminder value
*/

func updateBookReminder(){
self.databaseFunctions.group.enter()
self.databaseFunctions.updateBookReminder(orginalBook:
book)
self.databaseFunctions.group.notify(queue: .main) {
self.removeReminderContraints()
}
}

Update Database with Reminder Date


Open the “DatabaseFunctions” class and add the method defined in
Table 17-7. This method updates the book Record type for the selected
book with the updated reminder date.

426
Chapter 17 Book Reminder

Table 17-7. Update Reminder iCloud Repository


//MARK: - Update Reminder - Private Database

/*
When reminder is set updateBookReminder updates the book
record with the reminder date
*/

func updateBookReminder(orginalBook: Book){


let pred = NSPredicate(format: "bookname == %@ &&
authorname == %@",orginalBook.bookname, orginalBook.
authorname)
let query = CKQuery(recordType: "Book", predicate:
pred)
let operation = CKQueryOperation(query: query)
let ckRecordZoneID = CKRecordZone(zoneName:
"SharedZone")
operation.zoneID = ckRecordZoneID.zoneID

let sharedDatabase = CKContainer.default().


privateCloudDatabase
CKContainer.default().privateCloudDatabase.
add(operation)
operation.recordFetchedBlock = { record in

record["reminder"] = orginalBook.reminder
sharedDatabase.save(record, completionHandler:
{ (record, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print(error)
}
(continued)

427
Chapter 17 Book Reminder

Table 17-7. (continued)

else {
self.syncQueue.async {
let book = Book()

book.bookname = record!.
object(forKey: "bookname")! as?
String
book.authorname = record!.
object(forKey: "authorname")!
as? String
book.genre = record!.
object(forKey: "genre")! as?
String
book.status = record!.
object(forKey: "status")! as?
String
self.book = book
self.group.leave()
}
}
}
})
}
}

Remove Reminder Screen


This function, as shown in Table 17-8, removes all UI Objects of the
reminder Popup screen.

428
Chapter 17 Book Reminder

Table 17-8. Remove Reminder Constraint


//MARK: - Remove Constraints
/*
Removing the Reminder Popup view
*/
func removeReminderContraints(){
largerPopUpView.removeConstraints(largerPopUpView.
constraints)
largerPopUpView.removeFromSuperview()
popUpView.removeConstraints(popUpView.constraints)
popUpView.removeFromSuperview()
reminderLabel.removeConstraints(reminderLabel.
constraints)
reminderLabel.removeFromSuperview()
reminderdatePicker.removeConstraints(reminderdatePick
er.constraints)
reminderdatePicker.removeFromSuperview()
reminderPopUpImageView.removeConstraints(reminderPopUp
ImageView.constraints)
reminderPopUpImageView.removeFromSuperview()
}

Summary
In this chapter, we learned how to use the default iOS Notification center to
pop up reminders on books (example, you want to set a reminder to read a
book) on a particular date and time.
The next chapter will conclude the Book Detail screen. We will learn
how to make a Book Favorite.

429
CHAPTER 18

Mark Favorite
In the main display screen, we have a section dedicated to Favorite books.
Any book that is marked as favorite is shown in that section, regardless of
the Book Status (Read/Reading/To be Read).

Figure 18-1. Favorite Screen


© Shantanu Baruah and Shaurya Baruah 2023 431
S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_18
Chapter 18 Mark Favorite

Following is the progress made so far on Book Detail Screen:

• Share Book Record - Complete

• Edit Book Record - In Progress

• Delete Book Record

• Book Notes

• Setting Book Reminder

• Marking Book as Favorite

Frequency Button Action


The Frequency Display block button has a method attached for the
touchUpInside event. It is a toggle switch – if a favorite is marked, the
color of the favorite block is set to gray. Also, the book database table
is updated to indicate that the book is marked as favorite. The code in
Table 18-1 has the relevant code details.

Table 18-1. Frequency Button Action Function


//MARK: - Favorite Section
/*
frequencyButtonAction is called when Frequency button is

clicked. It toogles the favorite status and saves the
status in the iCloud repository
*/

@objc func frequencyButtonAction(sender: UIButton){


if(book.favorite == 0){
book.favorite = 1
favButton.backgroundColor = .lightGray
favTextLabel.backgroundColor = .lightGray
(continued)

432
Chapter 18 Mark Favorite

Table 18-1. (continued)


}else{
book.favorite = 0
favButton.backgroundColor = .white
favTextLabel.backgroundColor = .white
}
self.databaseFunctions.group.enter()

self.databaseFunctions.updateBookFavorite(orginalBook:
book)
self.databaseFunctions.group.notify(queue: .main) {
let alertController = UIAlertController(title:
"Favorite Alert", message: "Book '\(self.book.
bookname!)' is Saved", preferredStyle: .alert)

// Create the actions


let okAction = UIAlertAction(title: "OK", style:
UIAlertAction.Style.default) {
UIAlertAction in
self.tabBarController?.selectedIndex = 0
}

// Add the actions


alertController.addAction(okAction)

// Present the controller


self.present(alertController, animated: true,
completion: nil)
}
}
(continued)

433
Chapter 18 Mark Favorite

Table 18-1. (continued)

1. @objc func frequencyButtonAction(sender: UIButton){

This is the custom-defined function linked to the Touch Up Inside event.

2. if(book.favorite == 0){
3. book.favorite = 1
4. favButton.backgroundColor = .lightGray
5. favTextLabel.backgroundColor = .lightGray
6. }else{
7. book.favorite = 0
8. favButton.backgroundColor = .white
9. favTextLabel.backgroundColor = .white
10. }

In the Book Record Type for the field favorite, we store 1 for book marked as
favorite, otherwise the field will store 0. Line 8–16 is checking for the favorite
status and making the background gray if it is marked as a favorite book,
otherwise the background color is set to white.

11. self.databaseFunctions.group.enter()
12. self.databaseFunctions.updateBookFavorite(orginalBook: book)
13. self.databaseFunctions.group.notify(queue: .main) {
1 4. let alertController = UIAlertController(title: "Favorite
Alert", message: "Book '\(self.book.bookname!)' is Saved",
preferredStyle: .alert)
15. // Create the actions
(continued)

434
Chapter 18 Mark Favorite

Table 18-1. (continued)

1 6. let okAction = UIAlertAction(title: "OK", style:


UIAlertAction.Style.default) {
17. UIAlertAction in
18. self.tabBarController?.selectedIndex = 0
19. }

The Database Function updateBookFavorite is called to save the Favorite status.


After saving it, the user is shown an alert indicating that the book is marked as
favorite.

20. // Add the actions


21. alertController.addAction(okAction)
22. // Present the controller
23. self.present(alertController, animated: true, completion: nil)

Line 26–29 is presenting the alert on the screen


24. }
25. }

Database Functions Update Favorite Status


Open the “DatabaseFunctions” class and add the function defined in
Table 18-2 to update the book record favorite field with user-defined value.

435
Chapter 18 Mark Favorite

Table 18-2. Update Favorite Status


//MARK: - Update Favorite - Private Database
/*
When favorite is set updateBookFavorite updates the book
record with the favorite flag
*/
func updateBookFavorite(orginalBook: Book){
let pred = NSPredicate(format: "bookname == %@ &&
authorname == %@",orginalBook.bookname, orginalBook.
authorname)
let query = CKQuery(recordType: "Book", predicate:
pred)
let operation = CKQueryOperation(query: query)
let ckRecordZoneID = CKRecordZone(zoneName:
"SharedZone")
operation.zoneID = ckRecordZoneID.zoneID
let sharedDatabase = CKContainer.default().
privateCloudDatabase
CKContainer.default().privateCloudDatabase.
add(operation)
operation.recordFetchedBlock = { record in
record["favorite"] = orginalBook.favorite
sharedDatabase.save(record, completionHandler:
{ (record, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print(error)
}
(continued)

436
Chapter 18 Mark Favorite

Table 18-2. (continued)


else {
self.syncQueue.async {
let book = Book()
book.bookname = record!.
object(forKey: "bookname")! as?
String
book.authorname = record!.
object(forKey: "authorname")!
as? String
b ook.genre = record!.object(forKey: "genre")!
as? String
book.status = record!.
object(forKey: "status")! as?
String
book.favorite = record!.
object(forKey: "favorite")! as?
Int64
self.book = book
self.group.leave()
}
}
}
})
}
}

437
Chapter 18 Mark Favorite

Animation Function
The Animation function helps to give the currently tapped block a
bouncing effect. The code detailed in Table 18-3 has the relevant details.

Table 18-3. Animation Code


//MARK: - Animation
/*
Animation takes the UIButton as an input and provides a
bouncing effect for the button using the animate function
*/

fileprivate func animation(sender: UIButton){


sender.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
UIView.animate(withDuration: 2.0,
delay: 0,
usingSpringWithDamping:
CGFloat(0.20),
initialSpringVelocity:
CGFloat(6.0),
options: UIView.
AnimationOptions.
allowUserInteraction,
animations: {
sender.transform =
CGAffineTransform.
identity
},
completion: { Void in() }
)
}
(continued)

438
Chapter 18 Mark Favorite

Table 18-3. (continued)

1. fileprivate func animation(sender: UIButton){

Custom function to perform the bouncing effect. It takes Button that need to get the
bounce affect as an input parameter

2. sender.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)


every UI object has a transform variable – the CGAffineTransform is used to rotate,
scale, or skew the object.

3. UIView.animate(withDuration: 2.0,
4. delay: 0,
5. usingSpringWithDamping: CGFloat(0.20),
6. initialSpringVelocity: CGFloat(6.0),
7. options: UIView.AnimationOptions.allowUserInteraction,
8. animations: {
9. sender.transform = CGAffineTransform.identity
10. },
11. completion: { Void in() }
12. )
On the UI View, we are invoking the animation function. It takes three parameters –
time duration, any delay that we need to define for the animation, and the object to
animate.

13. }

439
Chapter 18 Mark Favorite

Summary
In this chapter, we learned how to set up a book as Favorite. The books
marked as Favorite show up in the display screen under the Favorite
section, giving easy access to users’ favorite books.
This concludes the Book Detail Section of the book. In the next
chapter, we will learn how to display the shared books from other users in
the Shared Tab of the Book Tracker App.

440
CHAPTER 19

Shared Books Tab


Sharing content across users of your application is a powerful feature that
iOS offers. As we have learned before, iOS takes privacy and security of
user generated content with extreme seriousness. In our App any content
created by the user is by default stored in a private database, which only
the user, who is the owner of the content, has access to. If a user decides
to share his/her content with another group of users, it must be explicitly
requested, and upon completion of the share the record is then visible to
all users at once. iOS stores such records in a Shared Database, and any
CRUD function on such records is then visible to all.
In our Book Tracker App, users can share a book with other user(s).
The Shared books Tab displays all books shared by other users. The code
written here is the same as our Main Book Display and Book Details
View Controller except all CRUD functions are executed against a Shared
Database. There are few additions / Changes that we need to do to make
Shared Books work. This section captures all those additional details that
you have to learn to enable Shared Book functionalities.

Accept the Share Record: Scene Delegate


The request for a share book will come as a text message (as explained in
Share Book Record in the Book Details View Controller Section). When the
user clicks on the message, the following code will be executed

© Shantanu Baruah and Shaurya Baruah 2023 441


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_19
Chapter 19 Shared Books Tab

In the Default Scene Delegate file, add the following function.

internal func windowScene(_ windowScene: UIWindowScene,


userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.
Metadata) {
let tabController = self.window?.rootViewController as!
UITabBarController
let vc = tabController.viewControllers
let viewController = vc?[0] as! BooksViewController
let acceptSharing: CKAcceptSharesOperation = CKAcceptSh
aresOperation(shareMetadatas: [cloudKitShareMetadata])

acceptSharing.qualityOfService = .userInteractive
acceptSharing.perShareCompletionBlock = {meta, share,
error in
print("successfully shared")
}

acceptSharing.acceptSharesCompletionBlock = {
error in
guard (error == nil) else{
print(error!)
print("Error XX \(error?.localizedDescription
?? "No Error Code to Show")")
return
}
viewController.fetchShare(cloudKitShareMetadata)
}
CKContainer(identifier: cloudKitShareMetadata.
containerIdentifier).add(acceptSharing)
}

442
Chapter 19 Shared Books Tab

1 . internal func windowScene(_ windowScene: UIWindowScene,


userDidAcceptCloudKitShareWith cloudKitShareMetadata:
CKShare.Metadata) {
This is a system default function which is called when a user accepts the Share
record request from iMessage/WhatsApp or other supported Apps.
2. let tabController = self.window?.rootViewController as!
UITabBarController
As Our View is a Tab Controller view, we are getting access to the Tab Controller.
This is required because the function to execute, accept, share request is in our
BooksViewController (note this function can be in any class file).
3. let vc = tabController.viewControllers
The viewControllers attribute of type UITabBarController will give us access to
all its view controllers.
4. let viewController = vc?[0] as! BooksViewController
Using the index, we are getting reference to the first View Controller,
BooksViewController. This is because our custom method to accept the shared
record stored is defined in this class.
5. let acceptSharing: CKAcceptSharesOperation = CKAcceptShares
Operation(shareMetadatas: [cloudKitShareMetadata])
CKAcceptSharesOperation has the shared record meta data which we are
storing in a local variable acceptSharing
6. acceptSharing.qualityOfService = .userInteractive
7. acceptSharing.perShareCompletionBlock = {meta, share,
error in
8. print("successfully shared")
9. }

443
Chapter 19 Shared Books Tab

10. acceptSharing.acceptSharesCompletionBlock = {
11. error in
12. guard (error == nil) else{
13. print(error!)
14. print("Error XX \(error?.localizedDescription ?? "No
Error Code to Show")")
15. return
16. }
This block simply accepts the record and prints if there are any errors
17. viewController.fetchShare(cloudKitShareMetadata)
If there are no errors in accepting the shared record, in that case we are calling
our custom Fetch Share method defined in the BooksViewController. The
custom method takes CloudKit shared metadata as input. This variable will have
the reference to the shared record
18. }
19. CKContainer(identifier: cloudKitShareMetadata.
containerIdentifier).add(acceptSharing)
This line of code will inform the CloudKit container that are accepting the shared
record. Please note, this will only happen when the recipient user accepts the
Shared Record.
20. }

Share Record Function


The Share Book Function can be in any file. We are currently defining it
in the BooksViewController. Open the file and add the following lines
of code:

444
Chapter 19 Shared Books Tab

/*
This function is called when a book share is accepted by
a user. We will invoke this from Scene Delegate system
class. The function will retrieve the shared record and
store in User Shared Database in iCloud.
*/

func fetchShare(_ cloudKitShareMetadata: CKShare.Metadata){


let op = CKFetchRecordsOperation(recordIDs:
[cloudKitShareMetadata.rootRecordID])
op.perRecordCompletionBlock = { record, _, error in
guard error == nil, record != nil else{
print("error \(error?.localizedDescription ??
"No Error")")
return
}
DispatchQueue.main.async {
self.cloudKitTask = record
let name = (cloudKitShareMetadata.
ownerIdentity.nameComponents?.givenName ?? "")
+ " " + (cloudKitShareMetadata.ownerIdentity.
nameComponents?.familyName ?? "")

record?.setValue(name, forKey: "shareuser")

record?.setValue(Int64(cloudKitShareMetada
ta.participantPermission.rawValue), forKey:
"sharepremission")

let alert = UIAlertController(title: "Shared


Record Status", message: "Record succesfully
shared by \(name)", preferredStyle: .alert)

445
Chapter 19 Shared Books Tab

alert.addAction(UIAlertAction(title: "OK",
style: .default, handler: { action in
switch action.style{
case .default:
print("default")
case .cancel:
print("cancel")

case .destructive:
print("destructive")
@unknown default:
print("error")
}}))
self.present(alert, animated: true, completion:
nil)
//self.querySharedTasks()

let sharedDatabase = CKContainer.default().


sharedCloudDatabase
sharedDatabase.save(record!,
completionHandler: { (record, error) -> Void in
DispatchQueue.main.async {
if let error = error {
print(error)
}
return
}
})
}
}
CKContainer.default().sharedCloudDatabase.add(op)
}

446
Chapter 19 Shared Books Tab

1. func fetchShare(_ cloudKitShareMetadata: CKShare.Metadata){


This is our custom method which will fetch the shared record and store it in the
Shared Database. The methods take shared MetaData as input.
2. let op = CKFetchRecordsOperation(recordIDs:
[cloudKitShareMetadata.rootRecordID])
3. op.perRecordCompletionBlock = { record, _, error in
4. guard error == nil, record != nil else{
5. print("error \(error?.localizedDescription ?? "No
Error")")
6. return
7. }
8. DispatchQueue.main.async {
9. self.cloudKitTask = record
10. let name = (cloudKitShareMetadata.ownerIdentity.
nameComponents?.givenName ?? "") + " " +
(cloudKitShareMetadata.ownerIdentity.nameComponents?.
familyName ?? "")
11. record?.setValue(name, forKey: "shareuser")
12. record?.setValue(Int64(cloudKitShareMetadata.
participantPermission.rawValue), forKey: "sharepremission")
We are retrieving the record from the shared user repository. Once retrieved, we
are storing all relevant attributes in a locally defined variable called record of
type CloudKitTask.
13. let alert = UIAlertController(title: "Shared Record
Status", message: "Record successfully shared by \(name)",
preferredStyle: .alert)
14. alert.addAction(UIAlertAction(title: "OK", style:
.default, handler: { action in

447
Chapter 19 Shared Books Tab

15. switch action.style{


16. case .default:
17. print("default")
18. case .cancel:
19. print("cancel")
20. case .destructive:
21. print("destructive")
22. @unknown default:
23. print("error")
24. }}))
25. self.present(alert, animated: true, completion: nil)
After retrieving the record successfully, the lines of the preceding code display an
alert stating the Book is shared.
26. let sharedDatabase = CKContainer.default().
sharedCloudDatabase
27. sharedDatabase.save(record!, completionHandler: {
(record, error) -> Void in
28. DispatchQueue.main.async {
29. if let error = error {
30. print(error)
31. }
32. return
33. }
34. })
35. }
36. }
37. CKContainer.default().sharedCloudDatabase.add(op)
The preceding line of code stores the fetched shared record in the Shared
Database. This completes the Shared Record retrieval and storing part. We can
now query the Shared Database and display it on our Shared Tab Display View.
38. }

448
Chapter 19 Shared Books Tab

Shared Book Database Function


In this section, we will query the shared database to retrieve all book
records which are shared by other users. There are two distinct differences
from what you have learned so far about querying database:
• Record Zone: Every shared user record is stored in a
unique system-defined record zone. This is done so
that the access is restricted to only the required users.
Zones provide that logical separation to ensure only
relevant users have access to the shared records. For
Querying all shared records by different Book Tracker
users, we need to query all available user record zones.

• Shared Database: Until now our queries were against


the private database. For shared books, we need to
query the shared database instead.

Shared Task Zones


This is to query all unique record zones for all books shared by other users.
Open DataBaseFunctions class and add the following code.

449
Chapter 19 Shared Books Tab

//MARK: - Get the Zones

/*
Before doing any query on Shared Databases we need to
retrieve all zones that the other users have shared. The
sharedTasksZones function retrieves all such zone and
stores it in a class level variable sharedRecordZones
*/

func sharedTasksZones(period: String){


let sharedData = CKContainer.default().
sharedCloudDatabase
sharedData.fetchAllRecordZones { (recordZone, error)
in DispatchQueue.main.async {
if error != nil {
print(error?.localizedDescription ?? "No
Error")
self.group.leave()
}
self.syncQueue.async {
if let recordZones = recordZone {
self.sharedRecordZones = recordZones
if(period == CommonVariables.Query_ALL){
self.sharedBookQuery(period:
period, recordZone: recordZones,
counter: 0)
}
}
}
}}
}

450
Chapter 19 Shared Books Tab

1. func sharedTasksZones(period: String){


This is a custom function which will be called first before querying the table. This
function will find all available zones.
2. let sharedData = CKContainer.default().sharedCloudDatabase
3. sharedData.fetchAllRecordZones { (recordZone, error) in
DispatchQueue.main.async {
4. if error != nil {
5. print(error?.localizedDescription ?? "No Error")
6. self.group.leave()
7. }
8. self.syncQueue.async {
9. if let recordZones = recordZone {
10. self.sharedRecordZones = recordZones
11. if(period == CommonVariables.Query_ALL){
12. self.sharedBookQuery(period: period, recordZone:
recordZones, counter: 0)
13. }
14. }
15. }
We are fetching all the record zones and then calling our custom Query Function.
Notice, we passed all the record zones an input parameter.
16. }}
17. }

Query Functions
This function will be called in a loop. In each iteration, we will retrieve the
associated records from the specified record zone.

451
Chapter 19 Shared Books Tab

// MARK: - Retrieve All Shared Books

/*
sharedBookQuery function is called on a repeat till all
shared zones are queried for books shared by other users
*/
func sharedBookQuery(period: String, recordZone:
[CKRecordZone], counter: Int) {
let pred = NSPredicate(value: true)
let query = CKQuery(recordType: "Book", predicate:
pred)
let operation = CKQueryOperation(query: query)
CKContainer.default().privateCloudDatabase.add(operation)

let ckRecordZoneID = CKRecordZone(zoneName:


"SharedZone")
operation.zoneID = ckRecordZoneID.zoneID
let sharedData = CKContainer.default().
sharedCloudDatabase

if(recordZone.count != 0){
sharedData.perform(query, inZoneWith:
recordZone[counter].zoneID) { results, error in
DispatchQueue.main.async {
if let error = error {
print("Cloud Query Error - Fetch
Establishments: \(error)")
self.group.leave()
}else if let books = results {
self.syncQueue.async {
for task in books{
let record = Book()

452
Chapter 19 Shared Books Tab

record.bookname = task.
object(forKey: "bookname") as?
String
record.authorname = task.
object(forKey: "authorname") as?
String
record.recordID = task.recordID
record.genre = task.object(forKey:
"genre") as? String
record.status = task.
object(forKey: "status") as? String
record.reminder = task.
object(forKey: "reminder") as? Date
record.favorite = task.
object(forKey: "favorite") as? Int64

self.books.append(record)

if(recordZone.count == counter + 1){


self.group.leave()
}else{
self.sharedBookQuery(period:
period, recordZone:
recordZone, counter: counter
+ 1)
}
}
}
}
}}

453
Chapter 19 Shared Books Tab

}else{
self.group.leave()
}
}
Please note, we are using a counter to check if all record zones are exhausted;
when all zones are over, we leave the asynchronous loop by leaving the group.
There is no difference in this query from what we have done before, except the
following:

• The recordZone input parameter is an array and we use a counter to provide


zone ID input to inZoneWith parameter in an iterative manner to perform the
query.
• Once the query is performed and controls return to the block, we are only
leaving the loop if the counter (which is incremented by 1) is equal to the total
count of the recordZone Array.
• If not, we are calling the same function again, but after incrementing the
counter variable by 1.

Summary
In this chapter, we learned how to accept a shared record and display the
records shared by other users on the Shared Tab of the Book Tracker App.
In the next chapter, we will learn how to Search a book from the iCloud
repository.

454
CHAPTER 20

Search Screen
This is the last Display screen we are going to code for our application.
Search will allow users to search for books using either the book name
or the author’s name. All books are stored in a Book Class variable called
Books. Based on the search input text provided by the user, we will search
for books and author names for any partial match. All books that will
match the search criteria will be stored in a temporary variable. The result
is then displayed using a table. When the user taps on a searched book
from the list, it will show the Book Detail Screen.

© Shantanu Baruah and Shaurya Baruah 2023 455


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_20
Chapter 20 Search Screen

Figure 20-1. Search Book Screen

Create the View Controller


Create a new View Controller with the name “SearchViewController” and
in the Main Storyboard, attach it to the search tab (last one).
The Search View Controller inherits Table View Delegate, and Table
view Data source to show the search results in a table, and Text Field
Delegate to dismiss the keyboard when user taps on the return key on the
Search Input Screen. The code details are defined in Table 20-1.

456
Chapter 20 Search Screen

Table 20-1. Class Inheritance


class SearchViewController: UIViewController,
UITableViewDelegate, UITableViewDataSource,
UITextFieldDelegate {

Class Variables
We will have three class level variables. A variable to access the
common functions class for drawing the screen, a variable to access
DatabaseFunctions class to access the function to query the database and
a variable that will hold the books’ user search results. Table 20-2 has the
relevant code details.

Table 20-2. Class Variables


//MARK: - Class Variables

/*
The below two variables are used to access the common
class functions and variables
*/
let commonFunctions = CommonFunctions()
let databaseFunctions = DatabaseFunctions()

/*
The search books variable defined below will hold the
books that meets the user search criteria
*/
var searchbooks = [DatabaseFunctions.Book]()

457
Chapter 20 Search Screen

Class UI Objects
We will have the following UI Objects for the search screen:

• A Label to stating “Search Books”


• A Text Field where user can input book or author name
to search

• A Table View to display searched books

• Table 20-3 has the UI Objects definition.

Table 20-3. UI Object


// MARK: - UI Objects

//Following UI Objects is required for the Search Screen

/*
A Label to define the Search Screen
a Search Textfied where user can input his/her search
criteria
a table view to diaplay the search result
*/
let applicationLabel:UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints =
false
label.backgroundColor = .white
label.numberOfLines = 4
label.textColor = .black
label.textAlignment = .center
return label
}()
(continued)

458
Chapter 20 Search Screen

Table 20-3. (continued)

let searchTextField:UITextField = {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints =
false
textField.backgroundColor = .white
textField.borderStyle = .roundedRect
textField.attributedPlaceholder =
NSAttributedString(string: “Search Terms”, attributes:
[NSAttributedString.Key.foregroundColor: UIColor.gray])
textField.textColor = .black

return textField
}()

let searchTableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints =
false
tableView.backgroundColor = UIColor.init(red: 255/255,
green: 253/255, blue: 208/255, alpha: 1)
tableView.layer.cornerRadius = 5
tableView.layer.borderWidth = 1
tableView.layer.borderColor = UIColor.black.cgColor
tableView.isScrollEnabled = false
return tableView
}()

459
Chapter 20 Search Screen

Screen Setup
This section will setup the search screen and will draw all UI Objects

View Did Load


The system-defined viewDidLoad method is the first function which will
be called when the search view controller opens. From this method, we
will call our custom “bookQuery” function to get all books from the iCloud
database. As querying iCloud is a resource intensive operation, we will
first get all books in a local Book object array. All searches will be executed
against the local Book Array. The code details are in Table 20-4.

Table 20-4. Lifecycle Methods


//MARK: - Lifecycle Methods
/*
Leveraging the default viewDidLoad method to initiate a
custom function bookQuery to retrieve all books in an
array. Our search will be against a local variable to
avoid resource intensive operation
*/

override func viewDidLoad() {


super.viewDidLoad()
bookQuery()
}

460
Chapter 20 Search Screen

Book Query function


This function calls the Database Functions Book Query function and waits
for its completion. The code details are in Table 20-5.

Table 20-5. Book Query Function


//MARK: - Book Query

/*
The bookQuery function queries the database repository
for all books
*/
fileprivate func bookQuery(){
databaseFunctions.group.enter()
databaseFunctions.bookQuery()
databaseFunctions.group.notify(queue: .main) {
self.setup()
self.searchScreen()
}
}

Database Function for Book Query


Open the DatabaseFunctions class and add the function as shown in
Table 20-6. This is the iCloud function to query for all available books.
Notice that the predicate is set to true (no condition set, hence will retrieve
all books).

461
Chapter 20 Search Screen

Table 20-6. Book Query Repository Function


// MARK: - Retrieve All Books

/*
bookQuery queries all books from the iCloud repository
*/

func bookQuery() {
let pred = NSPredicate(value: true)
let query = CKQuery(recordType: "Book", predicate:
pred)
let operation = CKQueryOperation(query: query)
CKContainer.default().privateCloudDatabase.
add(operation)
let ckRecordZoneID = CKRecordZone(zoneName:
"SharedZone")
operation.zoneID = ckRecordZoneID.zoneID
operation.recordFetchedBlock = { record in
let book = Book()
book.bookname = record["bookname"]
book.authorname = record["authorname"]
book.genre = record["genre"]
book.status = record["status"]
book.reminder = record["reminder"]
book.favorite = record["favorite"]
book.recordID = record.recordID

if(book.favorite == 1){
self.favbooks.append(book)
}
self.books.append(book)
}
(continued)

462
Chapter 20 Search Screen

Table 20-6. (continued)

operation.queryCompletionBlock = { [unowned self]


(cursor, error) in
DispatchQueue.main.async {
if let error = error {
print(error)
}else {
self.syncQueue.async {
self.group.leave()
}
}
}
}
}

Setup
Open your SearchViewController and add the function as shown in
Table 20-7. The setup function is called once the query is complete and
control is returned to the waiting asynchronous function.

463
Chapter 20 Search Screen

Table 20-7. Setup Function


/*
The setup function is called after the book query is
completed to set the search table, which uses a custom
cell for displaying search results. Setup will also set
the delegate for the textfield.
There are three events defined for the textfield to build
a dynanmic search table. As user type, the table will be
populated with results. The Textfields will help us to
achieve the same
*/
func setup(){
searchTableView.delegate = self
searchTableView.dataSource = self
searchTableView.register(BookTableViewCell.self,
forCellReuseIdentifier: "searchcell")
searchTableView.backgroundColor = UIColor.init(red:
202/255, green: 202/255, blue: 202/255, alpha: 1)
searchTextField.delegate = self
searchTextField.addTarget(self, action: #selector(text
FieldEditingChanged), for: .editingChanged)
searchTextField.addTarget(self, action: #selector(text
FieldEditingDidBegin), for: .editingDidBegin)
searchTextField.addTarget(self, action: #selector(text
FieldEditingDidEnd), for: .editingDidEnd)
}
(continued)

464
Chapter 20 Search Screen

Table 20-7. (continued)

1. func setup(){
Definition of the custom setup function
2. searchTableView.delegate = self
3. searchTableView.dataSource = self
4. searchTableView.register(BookTableViewCell.self,
forCellReuseIdentifier: "searchcell")
5. searchTableView.backgroundColor = UIColor.init(red:
202/255, green: 202/255, blue: 202/255, alpha: 1)
Setting up the table view delegate and data source reference. Notice that the Cell
Type is custom (this was defined and explained before). We are also setting the
table background color.
6. searchTextField.delegate = self
7. searchTextField.addTarget(self, action: #selector(textFiel
dEditingChanged), for: .editingChanged)
8. searchTextField.addTarget(self, action: #selector(textFiel
dEditingDidBegin), for: .editingDidBegin)
9. searchTextField.addTarget(self, action: #selector(textFiel
dEditingDidEnd), for: .editingDidEnd)
Setting the text field delegate. Also, defining three system-defined events for the
text field. The first is the when the value change on the text field (as the user
types we will do dynamic search, this event will be used to search as user types
and will display the result), the second function is invoked when editing begins
(when editing begins, we need to initialize the Search variables so that we are
ready for executing the search. We need to make sure Books class variable has
all books and search book class variable is empty), and the third function is when
the Editing ends (we need to reset the Book class variables so that we are ready
for the next search).
10. }

465
Chapter 20 Search Screen

Draw Search Screen


After the setup function, searchScreen is the next function (as shown in
Table 20-8) called after the book query is completed. This draws the screen
label and the text field.

Table 20-8. Draw Screen Function


/*
The custom function searchScreen is called after the book query
is done (along with the setup function) and it draws two UI
Objects on the screen - the label, and the search text field
*/
func searchScreen(){
let fontTaskHeader = UIFont.systemFont(ofSize: 30,
weight: .bold)
let attributesMainHeader: [NSAttributedString.Key: Any] = [
.font: fontTaskHeader
]
view.addSubview(applicationLabel)
commonFunctions.setFieldLayout(mainField:
applicationLabel, constraintField: view!, topAnchor:
40, leftAnchor: 0, rightAnchor: 0, heightAnchor: 40)

applicationLabel.attributedText = NSAttributedString
(string: "Search Books", attributes: attributesMainHeader)

view.addSubview(searchTextField)
commonFunctions.setFieldLayout(mainField: searchTextField,
constraintField: applicationLabel, topAnchor: 60,
leftAnchor: 5, rightAnchor: -5, heightAnchor: 40)
}

466
Chapter 20 Search Screen

Text Field Function


Our search is dynamic, as the user types, the search table is populated.
There are three events which will help us to meet this functionality.

Editing Begin
When the Editing Begins event is triggered, we will assign all books
(stored in DatabaseFunctions books variable) to the class level variable
Searchbooks. Code details are defined in Table 20-9.

Table 20-9. Uitextfield Editing Did Begin


/*
The event textFieldEditingDidBegin is triggered when the
Search Textfiled get the focus - we are just making both
the books and searchbooks variable to contain all books
*/

@objc func textFieldEditingDidBegin(sender: UITextField){


searchbooks = databaseFunctions.books
}

Editing Changed
Every time a user types in a letter, this function is called. As we will do
dynamic search, we will search for the books, draw the table, and refresh
the table, every time the user types in a letter. The code details are in
Table 20-10.

467
Chapter 20 Search Screen

Table 20-10. Uitextfield Changed


/*
textFieldEditingChanged event is triggered every time a
user types. The function will do the following:
- copy the search results in the books variable, as this
will be used to display the record in the table
- takes the currently typed text as input and searches
it in the original searchbooks variable for both book
name and author name. The result is stored in the books
variable
- Remove the table from the view
- and if any books are found we are making the table
visble and reloading the data. If not, the table is
hidden
*/

@objc func textFieldEditingChanged(sender: UITextField) {

databaseFunctions.books = searchbooks
databaseFunctions.books = searchbooks.filter {
item in return item.bookname.lowercased().
contains(searchTextField.text!.lowercased())
}
databaseFunctions.books.append(contentsOf:
searchbooks.filter {
item in return item.authorname.lowercased().
contains(searchTextField.text!.lowercased())
})
removeTableConstraints()
drawSearchTable()
(continued)

468
Chapter 20 Search Screen

Table 20-10. (continued)

if(databaseFunctions.books.count > 0){


searchTableView.isHidden = false
searchTableView.reloadData()
}else{
searchTableView.isHidden = true
}

}
1. @objc func textFieldEditingChanged(sender: UITextField) {
2. databaseFunctions.books = searchbooks
As you recall, when editing begins, we assign all database functions books to
the search book variable (this is because, for displaying the books, the table
view refers to “databaseFunctions.books” variable. “searchbooks” variable is
used as a temp variable to hold all books, and the “databaseFunctions.books”
variable will have a subset of books based on user search).
3. databaseFunctions.books = searchbooks.filter {
4. item in return item.bookname.lowercased().
contains(searchTextField.text!.lowercased())
5. }
We are now searching to see if any book name contains the user inputted
string – notice we are using contains with case insensitive search, so all books
will be filtered where the user-typed string exists anywhere in the book name.
6. databaseFunctions.books.append(contentsOf: searchbooks.
filter {
7. item in return item.authorname.lowercased().
contains(searchTextField.text!.lowercased())
8. })
(continued)

469
Chapter 20 Search Screen

Table 20-10. (continued)

Next, we are searching if any author name contains the user inputted string –
notice we are using contains with case insensitive search, so, all books will be
filtered where the user-typed string exists anywhere in the book name. notice we
are appending the value to “databaseFunctions.books” variable. So, now the
“databaseFunctions.books” variable has all books that meet the search criteria.
9. removeTableConstraints()
Remove the table drawn before. This is a custom function. The definition of the
same is explained in the latter part of this section.
10. drawSearchTable()
Draw the table. This is a custom function. The definition of the same is explained
in the latter part of this section.
11. if(databaseFunctions.books.count > 0){
12. searchTableView.isHidden = false
13. searchTableView.reloadData()
14. }else{
15. searchTableView.isHidden = true
16. }
If the search didn’t result in any books, we will keep the table hidden, otherwise
we will display the table and reload all values.
17. }

Editing End
When editing ends, we will store the “searchBooks” variable (which has
all books) to “databaseFunctions.books” variable. This way, we rest
the variables and are ready for another search. The code details are in
Table 20-11.

470
Chapter 20 Search Screen

Table 20-11. Uitextfield Editing Did End


/*
The event textFieldEditingDidEnd is triggered when the
editing ends on the search text field. searchbooks
variable, which has the entire search result is stored
back to the books variable. This is done, so that if a new
search is initiated we have all the variables reset to the
original values.
*/

@objc func textFieldEditingDidEnd(sender: UITextField){


databaseFunctions.books = searchbooks
}

Pressing the Return Key on Keyboard


The code in Table 20-12 will dismiss the keyboard.

Table 20-12. Uitextfield Dismiss Keyboard


/*
When user clicks on the return button we will dismiss the
kepyboard using the function below
*/

func textFieldShouldReturn(_ textField: UITextField) ->


Bool{
searchTextField.resignFirstResponder()
return true
}

471
Chapter 20 Search Screen

Drawing the Table


The code in Table 20-13 will draw the table when search is completed

Table 20-13. Draw Search Table


/*
The custom function drawSearchTable draws the Search
Table View, and is called every time a user types a
string in the text field and there is a match to display
books.
*/

func drawSearchTable(){

let tableHeight:CGFloat = CGFloat(databaseFunctions.


books.count * 50)

view.addSubview(searchTableView)
commonFunctions.setFieldLayout(mainField:
searchTableView, constraintField: searchTextField,
topAnchor: 60, leftAnchor: 5, rightAnchor: -5,
heightAnchor: tableHeight)
}

Removing the Constraints


The code shown in Table 20-14 will remove the constraints as we draw the
search table.

472
Chapter 20 Search Screen

Table 20-14. Remove Constraints


//MARK: - Remove Constraints

/*
Removing the Table Constraints
*/
func removeTableConstraints(){
searchTableView.removeConstraints(searchTableView.
constraints)
searchTableView.removeFromSuperview()
}

Table Function
Table function as shown in Table 20-15 has the following default functions:

• Number of Rows in the Search Table View

• Drawing the Cells with searched books

• Height of the Row

• The Custom Cell has an UI button that displays the


book name. The button has a touchUpInside event
define, and when user clicks the button, it will display
the book detail screen

473
Chapter 20 Search Screen

Table 20-15. Table Rendering Functions


//MARK: - Table Functions
//This section implements all table view functions

/*
The numberOfRowsInSection function will let the table
know how many books to display
*/
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return databaseFunctions.books.count
}

/*
The cellForRowAt draws the cell and display the book and
author name
*/
func tableView(_ tableView: UITableView, cellForRowAt
indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifi
er: "searchcell", for: indexPath) as! BookTableViewCell
cell.bookLabelButton.contentHorizontalAlignment = .left
cell.bookLabelButton.setTitle(self.databaseFunctions.
books[indexPath.row].bookname + " by, " + self.
databaseFunctions.books[indexPath.row].authorname,
for: .normal)
cell.bookLabelButton.addTarget(self, action:
#selector(taskButtonAction), for: .touchUpInside)
cell.bookLabelButton.tag = indexPath.row
return cell
}
(continued)

474
Chapter 20 Search Screen

Table 20-15. (continued)


/*
The heightForRowAt function defines the height of cell to
be of 50 pixel
*/
func tableView(_ tableView: UITableView, heightForRowAt
indexPath: IndexPath) -> CGFloat {
return 50.0
}

/*
On the custom cell when user taps on any cell displaying
the book and the author name the taskButtonAction
function is invoked, which in turn open the book details
view controller
*/

@objc func taskButtonAction(sender: UIButton){


let viewController = BookViewController()
viewController.book = databaseFunctions.books[sender.
tag]
viewController.modalPresentationStyle =
.currentContext
viewController.modalTransitionStyle = .crossDissolve
self.present(viewController, animated: true,
completion: nil)
}

475
Chapter 20 Search Screen

Summary
This concludes our learning on creating a professional-looking Book
Tracking App. If you have followed through all the concepts in the book,
you must have on your iPhone a fully functional Book Tracker App with
data persistence in an iCloud repository. In the next section, we will learn
how to publish the application to the App Store.

476
CHAPTER 21

Packaging
and Releasing
Now that you have a fully functional professional looking App, it is
time now to learn how to package your App, get it tested in a controlled
audience, and finally, submit it to Apple App Store Review and approval.
Follow this chapter to learn the steps you need to follow to release your
App to the App Store.

Setting Up the Application Logo


Before you are ready to publish the App for testing and releasing to the
App store, we need to create a minimum set of App icons of the following
dimension (please note we are only creating an iPhone App):

1. 40w:40h Pixels

2. 60w:60h Pixels

3. 58w:58h Pixels

4. 87w:87h Pixels

5. 80w:80h Pixels

6. 120w:120h Pixels

© Shantanu Baruah and Shaurya Baruah 2023 477


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3_21
Chapter 21 Packaging and Releasing

7. 180w:180h Pixels

8. 1024w:1024h Pixels

You can use tools such as Photoshop to create the logos. I have used
GIMP, a freeware to create the Book Tracker App logo. Once you are done
with your design, you can then export the image to the relevant sizes to
PNG (or other file format) files.
Once the images are ready, please go to the navigator and click on
“Assets” and drag and drop the right image to the right image holder, as
shown in Figure 21-1.

Figure 21-1. Xcode Assets Screen

Build the Archive and Publish


We are now ready to Archive and Publish the App. Follow the
following steps:

478
Chapter 21 Packaging and Releasing

Set Up Test Flight Account


Login to https://appstoreconnect.apple.com using your apple ID. Once
logged in, click on “My Apps”, as shown in Figure 21-2.

Figure 21-2. App Store Connect – Home Screen

479
Chapter 21 Packaging and Releasing

Next, we need to create a shell for our App before we can publish the
App. Click on Add “New App”, as shown in the screen in Figure 21-3.

Figure 21-3. App Store Connect – New App Screen

480
Chapter 21 Packaging and Releasing

Add the information as shown in Figure 21-4 to the new application


screen: Name, Primary Language, Bundle ID (this is pre-populated
based on the applications you have created), SKU (This must be
unique), and user access. Note: To Learn more about Apple App Store
Connect, please refer to Apple developer documentation from the
following link.

https://developer.apple.com/support/app-store-connect/

Figure 21-4. App Store Connect – New App Popup Screen

481
Chapter 21 Packaging and Releasing

Menu Option
Once we have the shell ready, get back to the Xcode and from the Top
Menu, select Product ➤ Archive, as shown in Figure 21-5.

Figure 21-5. Archive Menu Option

Distribute App
If there are no errors in the code, the archive will complete successfully,
and you will be prompted with a screen as shown in Figure 21-6. Click the
“Distribute App” option.

482
Chapter 21 Packaging and Releasing

Figure 21-6. App Distribution

Keep the default option selected “App Store Connect” page as shown
in Figure 21-7.

Figure 21-7. App Distribution – App Store Connect

483
Chapter 21 Packaging and Releasing

Select the default “Upload” option, as shown in Figure 21-8.

Figure 21-8. App Distribution – Upload

Enter details on the Preparing App record, (Name, SKU, Primary


Language, and Bundle Identifier) – the details will be picked from your
App properties as shown in Figure 21-9.

Figure 21-9. App Distribution – Product Details

484
Chapter 21 Packaging and Releasing

In the next screen(as shown in Figure 21-10) select all the options.

Figure 21-10. App Distribution - Distribution Option

Use the automatic signing option, as shown in Figure 21-11.

Figure 21-11. App Distribution - Sign In Option

485
Chapter 21 Packaging and Releasing

And finally, upload the content, as shown in Figure 21-12.

Figure 21-12. App Distribution – Upload Option

Setting Up the App in App Store


Login to https://appstoreconnect.apple.com using your apple ID. Once
logged in, click on “My Apps” and select the App “MyTestLibrary”
Click on the first menu option “1.0 Prepare for Submission”. In this
screen, we need to enter the following information:

1. App Preview Screen for the relevant screen types

2. Promotional Text

3. Description of your App

4. Keywords, which will help to search your App in


App store

5. A Support URL – where users can reach in case of


questions or errors

486
Chapter 21 Packaging and Releasing

6. A Marketing URL, describing your App in detail

7. Version no. of the App

8. Copyright information

9. Build, you can choose from a list of released


versions

All this information will be used by the App Review board to review
your App. Once done, you can submit for review.

Figure 21-13. Application Setup

 romote Development Database


P
to Production Database
One of the actions we need to perform before releasing the App is to
promote our development database to the production environment.
Follow along the following steps to achieve the same:

487
Chapter 21 Packaging and Releasing

• Go to your CloudKit Console as shown. You can reach


the console by clicking on “CloudKit Console” from
the project settings.

Figure 21-14. Project Properties – Cloudkit Properties

• CloudKit Database

From the main screen, click the CloudKit Database block, as shown in
Figure 21-15.

Figure 21-15. Project Properties – Cloudkit Properties

488
Chapter 21 Packaging and Releasing

• Select Database

Select the database from the top drop down. And select the option
Deploy Schema Changes from the left tree menu options, as shown in
Figure 21-16.

Figure 21-16. Project Properties – Schema Properties

• Deploy Changes

You will be prompted with a screen confirming deployment. Go ahead


and deploy it, as shown in Figure 21-17. You are ready with your App
distribution process.

489
Chapter 21 Packaging and Releasing

Figure 21-17. Deployment Screen

Invite Test Users in Test Flight


Before releasing your App to the App Store, it is a good practice to get your
App tested by a set of users. You can do this through Test Flight. The Users
who will test your App need to be set up in the Appstoreconnect.com

490
Chapter 21 Packaging and Releasing

portal and the users need to download Testflight on their iPhone. When
you publish an App for testing it appears in the Test Flight App. Users can
then download the App from Test Flight and use it like a beta version.
While testing, users can directly send feedback to the Test Flight
Center. Also, in case of crashes, a screen capture can also be sent. We can
then analyze the crashes in our Xcode build, fix issues, and release new
patches for users to test. Follow the following step to setup test users:

• Login in to https://appstoreconnect.apple.com and


click on TestFlight. From there, you can invite users to
test your App, as shown in Figure 21-18.

Figure 21-18. App Store Connect Screen

491
Chapter 21 Packaging and Releasing

Test-Driven Development
Modern software development demands Test Driven Development (TDD).
TDD has many benefits as outlined in the following:

• The test cases dictate the development process, which


allows comprehensive code coverage for multiple user
scenarios.

• The documentation always stays current, increasing


the readability of the code.

• Helps in Peer based development.

• Future refactoring becomes easy to maintain.

When we are creating a project, we can choose to include unit test


cases, as shown in Figure 21-19.

Figure 21-19. Project Creation Screen – Include Tests.

492
Chapter 21 Packaging and Releasing

In the project, we can then create a class object of type UI Test Case
Class (as shown in Figure 21-20), and write our test method and code in
the class accordingly.

Figure 21-20. UI Test Case Class File Option

This book does not provide a comprehensive code for TDD


development. We encourage the users to use this as a base to learn and
implement TDD into your project.

Summary
This was the final chapter of our book. We have learned not only how to
interact with an iCloud repository for performing CRUD functions, but
also creating an aesthetically appealing professional looking App. We also
learned how to extensively manipulate display views using Table Views

493
Chapter 21 Packaging and Releasing

by customizing the Table Cells, use events best coding for better user
interaction, and code-based screen designs.
Extension is another important leverage we have in iOS coding to
embed functionalities beyond the core development environment. We
learned about notification and iCloud Extensions in this book. Finally, in
this chapter, we learned how to make our code ready to be deployed in
production and send for the App Review process.
We hope you had fun writing code and seeing your App come to
life as you went through the 21 chapters of the book. Our intent was to
provide a comprehensive learning in native iOS development and a strong
bedrock for you to explore the art of possibilities in iOS programming.
Please let us know the Apps you will create using the learnings of the book.
Happy coding.

494
Index
A save book function
alert control, 293
Add book
asynchronous loop, 293
Add view controller, 92, 93
author’s name, 290
designing, 88
canSave variable, 290
running code, 93, 94
custom function, 293
running program, 114
genre name, 290, 291
UI objects, 97, 99, 100, 102
iCloud function query,
UI text field, 102, 104, 106, 107,
292, 293
109, 110, 112
Ok action, 294
view controller, 89, 91
reset field, 294
Add Book Screen, 258, 259
statements, 293
Add Book Tab Bar, 257, 258
AddBookViewController status, 291
class objects, 294, 295 validation, 287–289
class variables, 296 var variable, 289
DataBaseFunctions Class, 294 save book record
genre and status objects attribute, 299
array objects, 270 custom function, 298
class and static variable, 269 iCloud, 296–298
CommonVariables class, 268 SharedZone, 298
genre class, 270 zone ID information, 299
genre setup screen load event
function, 270–275 add book screen, 276–278
object type, 270 functions, 266
status class, 270 setup function, 267, 268
status setup function, 275 table functions
reset function, 300 display cell value, 281

© Shantanu Baruah and Shaurya Baruah 2023 495


S. Baruah and S. Baruah, App Development Using iOS iCloud,
https://doi.org/10.1007/978-1-4842-8758-3
INDEX

AddBookViewController (cont.) remove reminder screen,


header, 282–284 428, 429
row height, 282 reset reminder, 424, 425
rows, 279, 280 save remainder, 418, 419
sections, 279 setup reminder, 419–423
select row action, 285 update reminder method,
text fields events 425, 426
keyboard should return, 286 iCloud repository, 426–428
visual display, 285 Books notes
UI objects adding notes
genre and status, 264, 265 cancel button, 405
input text field/author name CellforRowAt function, 401
input, 262, 263 custom function, 400, 401
navigation bar/save and If Save action, 405
reset, 261, 262 navigation bar function,
table views, 265 404, 405
variables, 260 popup screen, 402–404
AddBookViewController remove constraints,
method, 128 405, 406
BookNotesViewController,
377, 378
B custom table view cell, 386, 387
Backend as a Service (BaaS), 11 delete notes, 399, 400
Book detail screen, 376, 413, display screen, 388, 389
432, 455 iconButtonAction, 394, 401
bookQuery function, 149, 185 lifecycle and setup
Book reminder functions, 383–385
image view tap gestures, notesButtonAction function,
418, 419 376, 377
notification screen, 413, 414 query function, 385
permission request, 413, 414 save book method, 406–408
remainder popup iCloud repository, 408–410
screen, 415–418 tab bar function, 410
reminderButtonAction, 415 table view function, 389–394

496
INDEX

Tableview trailing swipe, class level variable, 339


394–398 definition, 339
UI objects, 380–383 draw cell object, 341, 342
variable, 378, 379 UI objects, 339–341
viewDidLoad function, 383 View load function, 304
books variable, 189 BookViewController class, 242
Book Tracker App, 441
controller, 71, 73, 75, 76
create application, 76–83 C
BookViewController CKQueryOperation function, 145
BookDetails CloudKit, 11
custom cell, 338 containers, 18
header height, 338 dashboard, 18
header view, 337 history, 11
row height, 336 key-value storage, 17, 18
rows, 333 parts, 17
sections, 333 services, 18
tab bar function, 342 setting up, 13, 14
table, displaying, 334, 335 Xcode iCloud, 15
book details screen, 311, 312 Xcode setting, 15–17
class inheritance, 302 CloudKit dashboard, 18
class variables, 304 accessing, 19
creation, 301 API access, 20
drawing screen, 328, 330–332 book record type, 25
functionalities, 301 Book Tracker App, 21, 23
set book details container permission, 20
function, 307–309 data, 20
setup function, 304–307 home screen, 20
UI objects, 312–314 indexes screen, 24
reminder popup screen, logs, 21
326, 327 schema, 21
six view blocks, 315–323 telemetry, 21
table/popup view, 324, 325 understanding, 19
UI table view cell usage, 21

497
INDEX

CloudKit framework, 144 defining alert, 60, 61


CloudKit function, 144, 147 display, 62
CommonFunctions class, 250 UI color, 45
CommonVariables class, 123 dynamic system colors, 48
Constraints, 192 getting system colors, 49
Core swift concepts guidelines, 46, 47
classes/structures, 28–30 system colors, 47
array, 30, 31 UIImageView
beautifying strings, 36 create, 53
functions, 34 methods, 55
life cycle methods, 37, 38 properties, 54
scope, 32, 34 UITableView
left/var, 27 cell heights, 68
loop controls create, 62
for-in loops, 42 delegates, 63
for-in loops, range, 43 display, 65, 66
while loop, 44 drawing, 64
syntax header, 67
CG color, 50 selection of row, 66
methods, 58–60 setting, 64
properties, 56 swiping function, 68
RGB, 49 UITextField, 56
UINavigationBar, 51 create, 56
type casting variables, 27
book class, 39 viewDidAppear, 38
count fiction, 41 viewDidDisappear, 39
defined color, 49 viewDidLoad, 38
fiction class, 40 viewWillAppear, 38
type book class, 41 viewWillDisappear, 39
types, 28 CRUD functions, 441
UIAlertController Custom-defined function, 171
alert/user action, 61, 62 Custom function, 190

498
INDEX

D cell screen, 234, 235


creation option, 231
DatabaseFunctions, 123, 143
custom button, 232, 233
DatabaseFunctions class, 145, 148,
custom cell, 231
184, 186, 190, 215, 218
functions, 236–239
DatabaseFunctions file, 181
header display, 240–243
DatabaseFunctions.swift file,
inheritance, 235
169, 182
top view blocks, 243, 244
Delegations
UI objects, 207–212, 214
calling, 254
view button action, 245
definition, 250
implement, 251, 252
protocol function, 254, 256
E
protocol variable, 254
Edit Book View Controller
reference setup, 253
class level variables, 361
Delete block
book detail screen, 369 code, 359, 360
DatabaseFunctions class, completeButtonAction
372, 373 function, 359
deleteButtonAction function, navigation bar, 361, 362
369, 370 save book, 364, 365
waiting block, 372 setup function, 363, 364
DispatchGroup functions, 147 update book record, 365–367
Display book screen editingChanged, 178
drawing screen
main display, 229, 230
top, 223, 225, 226, 228, 229
F, G, H
initial setup, 207 Favorite books
lifecycle method, 216 animation function, 438, 439
class-level variables, 215 favorite screen, 431
query books, 216–221 frequency button action
reading button action, 246, 247 function, 432–435
setup function, 221, 222 update favorite status, 435–437
table setup Fiction class, 40

499
INDEX

I, J P, Q, R
iCloud database Packaging and releasing
create function file, 123 App distribution, 482, 483
functions, 130 App store connect, 483
post save, 131, 133 options, 485
reset field, 134–136 product details, 484
save book record, 124, 126, 128 sign in option, 485
saving book, 123, 124 upload option, 484, 486
validation, 115, 118, 122 application icons, 477
values, 129 application logo, 478
application setup, 486, 487
App store connect
K home screen, 479
Key-value storage, 17 new App popup screen, 481
new App screen, 480
archive and publish
L menu option, 482
Life cycle methods, 111 test flight account, 479
development database to
production database
M CloudKit console, 488
modifyRecordsCompletion deployment screen, 489, 490
Block, 173 project properties, 488
modifyRecordsOperation, 173 select database, 489
steps, 487
test users, 490, 491
N Xcode assets screen, 478
Non-fiction class, 40
Notification Center, 200
S
Search book, 456
O class variables, 457
Object-oriented programming, 143 constraints, removing, 472

500
INDEX

screen setup CloudKit toolkit, 347


DatabaseFunctions, book share record functions
query, 461–463 bookShare function, 350–352
draw screen function, 466 screen simulator book tracking
query function, 461 App, 345, 346
setup function, 463–465 share book option, 346, 347
viewDidLoad method, 460 shareButtonAction, 348, 349
SearchViewController, 456 share record functions
table, drawing, 472 book record, 355
table function, 473–475 BookShare function, 349
text field function CKShare, 352, 353
editing begins, 467 error, 353
editing changed, 467–470 iCloud database, 354
editing ends, 470, 471 popup screen, 354
return key, 471 queue, 352
UI objects, 458, 459 recipient user, 349
Search screen, 175 recordToShare, 353
Shared books tab shared database, 354
query functions, 451, 453, 454 share screen, 355
scene delegate file, 442 sharing controller
acceptSharing, 443 screen, 354
CKAcceptShares waiting block, 355
Operation, 443 Storyboard, 4
CloudKit, 444 Swipe function, 165, 167, 168
Our View, 443 syncQueue, 146
viewControllers
attribute, 443
internal func T
windowScene, 443 Table delegate, 179
Shared database function, 449 Table functions, 192–194
Share record function, 444–448 Table view, 180
task zones, 449–451 aspects, 150
Sharing contents, 441 cellForRowAt, 156, 157
call back functions, 356 delegation, 152

501
INDEX

Table view (cont.) V, W


drawing, 154
Variable
objects, 151
definition, 186
run the program, 157
View controller
table cell, 152, 153
assigning, 141
table header, 162, 163
assignment, 142
text label, 158–161
create, 138, 175
taskRecords, 172
options, 140
Test driven development (TDD),
setup function, 177
492, 493
Test flight, 200
Text field, 177, 186 X, Y, Z
Xcode
U auto-completion, 3
UI objects, 176 install, 5
UITextFieldDelegate, 178 interface, 6–8
User searched books, 181 storyboard, 4

502

You might also like