100% found this document useful (2 votes)
890 views58 pages

SwiftUI Basics 2.0

Uploaded by

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

SwiftUI Basics 2.0

Uploaded by

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

BLCKBIRDS

THE
STUDY
OF CELL
BIOLOGY
Introduction to the building
blocks of life
Copyright © 2020 www.BLCKBIRDS.com

All rights reserved.

This production is the property of “www.blckbirds.com”.

No part of this production may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical,
photocopying, recording or otherwise, by you to any third party without the written permission of the operators of “www.blckbirds.com”.

Unauthorized reproduction or distribution of this content is punishable by law. Copyright infringements - including those that do not have a monetary
background - will not be tolerated and in any event the owner will seek damages and prosecution.
Important:

❗ We strongly recommend you that you not only read this book but
to try everything yourself immediately. This helps strengthen your
skills!

SwiftUI is still pretty new. This means that there can occur some bugs. It’s


also likely that Apple will update its framework more often than usual,
which can lead to some problems within the code used in this book.
However, whenever this is the case, this book will get updated as quickly
as possible. You can always contact us if you have some problems,
concerns, suggestions or wishes!
Table of content
Chapter 1: Preface

Chapter 2: Introduction to Xcode 12

Chapter 3: Building User Interfaces with SwiftUI

Chapter 4: Data Flow in SwiftUI

Chapter 5: Lists and NavigationViews

Chapter 6: Timer App

Chapter 7: Where to go from here


SHARE YOUR PROGRESS!
Tag @BLCKBIRDS or use the #swiftuibasics hashtag
to get shared on Instagram and Twitter!
Thank you! ♥

Chapter 1 First of all, we wanted to thank you for downloading this eBook
and taking the time to read it!

Preface This book will provide you with a detailed introduction to Apple's
newest app development framework, called SwiftUI. After a few
• What the SwiftUI framework is general words about SwiftUI and the fundamental principles
used for behind it, we'll go briefly through Xcode 12, Apple's in-house IDE
(Integrated Development Environment). If you have already
• How SwiftUI differs from the “old”
way of coding iOS apps using worked with Xcode before, you can feel free to skip this chapter!
storyboards and UIKit
We'll then learn how to use SwiftUI views to create user interfaces
• The principles and advantages of for iOS apps in a block-by-block approach! Next, we will dig
working with SwiftUI deeper and take a look at the data flow concepts used in SwiftUI.
Then, we will learn how to present data within Lists and how to
connect and navigate between multiple views, and finally, we will
get familiar with ObservableObjects and how to use them the as
the view's data model.

After that, you should already be able to write your own iOS
applications using SwiftUI!
What is SwiftUI? "
SwiftUI was announced by Apple at the WWDC19 and is
described as "an innovative, exceptionally simple way to
build user interfaces across all Apple platforms with the
power of Swift". And that's true, with SwiftUI, it's
surprisingly simple to build apps just like you imagine
them to look.

As a comparison: Until recently, the most common way to


build iOS apps was to use storyboards. Simplified, you
created the UI for your app by dragging and dropping,
arranging and constraining elements, and connecting
them to your code. But to see the final result, you were
forced to compile and run your app every time you made
some changes.

Because you had two "layers" that could easily differ from
each other, the storyboard canvas on the one hand and
the written code on the other, this approach often led to
bugs or crashes, for example when a connection from the
storyboard element and the code was missing.

With SwiftUI, Apple introduced a new framework that


works completely differently.

Building apps with old-school storyboards often leads to lack of visual feedback,
bugs and frustration.
SwiftUI follows a declarative syntax approach which means that we describe in code how our interface should look.

Instead of having two separate layers to work with, the You see that every part of the UI, the map, the circled
canvas for your app's interface and the code, SwiftUI image, and the texts are described using code.
combines both combines and merges them. This becomes
easier to understand when we familiarize ourselves with And that’s one great advantage when working with SwiftUI!
the approach of how SwiftUI works. Everything you write down as code gets instantly reflected
in a lightweight preview simulator. No time-intense run &
In a nutshell: In SwiftUI, you use code to describe how your compile workflow anymore! Everything you describe within
app should look. With the new syntax used in SwiftUI, you your code get’s immediately displayed to you as a preview.
simply describe how your interface should look like and
how it should behave. Take a look at the screenshot above.
And that's not all: You can even manipulate your app's UI was the common programming approach. But what's the
inside the preview simulator itself. For example, you can difference? Simplified: By writing code following a
drop and drag another text object or manipulate a specific imperative programming approach, we determine how we
object's color, as we'll see later. Every change in the react when the "state" of our app changes (a state is
preview simulator affects the corresponding code and vice something like a momentary snapshot of your app) and
versa! execute the relevant code. Following a declarative
programming approach instead, we describe our app's
This new workflow saves us a lot of time and simplifies app- interface for all possible states in advance. Here's a great
building tremendously. And because code and UI are article if you want to dive deeper into this topic. And don’t
always linked and dependent, you don't have to worry worry, you don’t really need to grasp this concept to start
about broken outlets and actions anymore as back in the building SwiftUI apps!
days of using storyboards.

Technical requirements for running


How does SwiftUI differ from working
SwiftUI $
with UIKit + storyboards❓
SwiftUI got shipped with Xcode 11. However, the latest
If you never developed iOS apps before, feel free to skip
SwiftUI features are only included with Xcode version 12.0
the next paragraph.
or higher, which you can download for free from the Mac
App Store. Note that SwiftUI is only compatible with
However, if you already worked with UIKit and storyboards,
running at least MacOS Catalina.
you're perhaps wondering how the programming
approach used in SwiftUI differs from UIKit and
storyboards. As you saw on the last page, the syntax used
in SwiftUI is somewhat different from what you used to
know when building iOS apps using UIKit. This is because
SwiftUI follows a declarative approach instead of an
imperative one. Until recently, imperative programming
Personal requirements !
Although we try to explain everything as easily as possible
and always step-by-step, we recommend being familiar
with the basics of the Swift programming language.

Important: The SwiftUI framework should not be mistaken


for the Swift programming language. Although we use
Swift to write the SwiftUI app's code, they are two
completely different things.

However, you don't have to be a professional programmer


to understand and apply what you will learn in this book. If
you are a beginner, we recommend reading our free Swift
5 Programming for Beginners eBook first to get you
started.

By the way, you don’t need to be familiar with building iOS


apps using UIKit, although it could give you a slight
advantage. But don’t worry when you don’t know what
we’re talking about right now!

That’s all, let’s get our hands dirty and start building some
awesome SwiftUI apps!
Before we are getting started with creating our first SwiftUI
projects, let’s take a quick tour through Xcode. 
Chapter 2 If you already know or have worked with Xcode before, feel free to

Introduction skip this chapter. However, if you are unfamiliar with iOS
development, make sure you read this chapter carefully, as it is
crucial for learning iOS development.

to Xcode12 What is Xcode used for? &


• Get to know Apple's in-house IDE
Xcode is Apple's in-house IDE (Integrated Development
• Basic structure, layout, and Environment). For developing iOS apps, Xcode is the software you
functionality of Xcode will work most often with. Therefore, it is very important to have a
profound knowledge of Xcode and to master the basics from
• How to create own apps using
Xcode scratch.

In Xcode, you compose the user interface of your app, organise


and write the code that makes your app run. Xcode also offers you
the possibility to run and test your app on a virtual simulator on
your Mac (and, of course, on a real iOS device).
Creating a new Xcode project
To get in touch with the IDE, just open Xcode and click on
"Create a new Xcode Project".

Next, click on "App“ under the "Multiplatform" section,


then on "Next" and give your app any name you want, for
example “DemoApp". Make sure you use "SwiftUI" as the
Interface and "SwiftUI App" as the Life cycle mode.

This feeling is normal and will vanish as soon as you get to


know the basic structure and functionality of Xcode.

Fortunately, the interface of Xcode is designed pretty


straight forward. The interface you see now basically
consists of five main sections. Don't worry if you don't see
all the sections just yet. We'll learn how to toggle the
different sections if we go through each of them one by
one.

These are: The Toolbar (1),  the Navigator Area (2), the
Editor Area (3), the Utility Area (4), and the Debug Area (5).
Don't worry if you don't see all the sections just yet. We'll
learn how to toggle the different sections if we go through
each of them.
The highlighted left and right buttons allow you to toggle the navigator and utility area.

The Toolbar '

Using the toolbar we can access the basic Xcode settings


(not to be mixed up with the settings of your app project)
and perform various operations. On the left side of the
toolbar, you can select the device on which you want to run
your app, for instance, on any simulator. The area in the
center tells you when Xcode is working on something.
Using the left and right buttons you can toggle the
navigator and utility area.

The Navigator Area (


The Navigator Area helps you finding your way around
your project and organizing your code and resources.

By default, the “Project Navigator” is selected (you


recognise this by looking at the highlighted symbol) and is
probably the most important mode of the navigator area.
This is where the different parts of your app’s code listed.
The more complex your app becomes, the more files your There are also other folders where platform-specific
project will contain. To keep track of them, you can create adjustments can be made. For example, the iOS group
“groups” (Xcode’s name for folders) and move the files only contains files that are relevant for running the app on
around as you like. Where you are placing files within your iOS devices.
project navigator, does not affect the logic of your code or
Although this course focuses on developing iOS apps
the behavior of the app.
using SwiftUI, all files can also be stored within the
When you click on a file, it will open in the Editor Area “Shared” group since those files can also be used for
where you can edit it. If not already done, click on running the apps we will create on macOS.
“ContentView.swift” to display this file in the Editor Area.
Swift files are the heart of every iOS App. In these, you The Editor Area ✍
write the code that makes your app run.

Excursus:

With Xcode 12 and SwiftUI, Apple is encouraging


developers even more to develop apps that run cross-
platform on iOS, iPadOS and macOS. 

This is noticeable by the fact that Xcode 12 automatically


generates so-called multiplatform app templates for us. In
the Navigator Area, you can see a group folder “Shared”,
which contains e.g. the view files that are used by all
platforms (iOS, iPadOS and macOS).
Here you write the code and compose the interface of your
app. The appearance of the Editor Area depends on which
file type is opened.

The Utility Area *


Similar to the Editor Area, the appearance of the Utility
Area depends on what file type you have selected. Here
you can access meta information, references, etc. of files
or/and their components. This area often confuses
beginners because the use of this area depends on the
particular situation. But the more you work with Xcode, the
more you will get the hang for it. You’ll see, it’s much easier
than it looks.
The Debug Area + Conclusion ,
This area opens once you run your app by clicking the run
Congratulations! You are now familiar with the basics of
symbol in the Xcode toolbar.
Xcode.
If you run your app you will find all relevant information like
Don’t worry if you ever feel overwhelmed. You’ll see that,
runtime error messages and executed print statements in
the more you deal with it, the more comprehensible
this area. This area becomes very important when it comes
everything becomes.
to finding and fixing errors and bugs inside your app. 
If you ever have problems with understanding something
discussed, do not hesitate to write a message.
Creating a SwiftUI Project !

Chapter 3 Let's dive right into it and start creating our first SwiftUI project. For
doing this, open Xcode and click on "Create a new Xcode project".

Building User Select "App" under the "iOS" tab as the application type and click
on Next.

inTerfaces
With SwiftUI
• How to setup a SwiftUI project

• How to build the interface of your


app using SwiftUI views You can give the project any name you want. In this example, we
use "SwiftUIDemo" as the Product Name. Make sure you use
• How to customize views using
"SwiftUI" as the Interface and "SwiftUI App" as the Life cycle mode.
modifiers
When you click on Next, you can choose where you want to save
• How to arrange views using Stacks
your Xcode project.

After creating a new SwiftUI project, Xcode should open the


default ContentView.swift file. If not, click on it in Xcode's navigator
area on the left side.
Now you should see a split-screen. The left side shows the
SwiftUI code used to build our app's interface. This code
consists of two blocks:

• The View struct: This struct is where we write our


code for building up our app's interface. The struct
itself contains a body property. Inside the body, we
create all the components we need for our UI,
arrange, and modify them.

• The Previews struct: This struct renders the related


View with its body and displays it in the preview
simulator on the split screen's right side.

Every time you make some changes within


your View struct, the Previews struct notices and displays
the updated View inside the preview simulator!

At default, Xcode inserts a Text view inside


the ContentView's body, reading "Hello World". Let's
modify this view by changing its String to "I love SwiftUI!”.

struct ContentView: View {


var body: some View {
Text("I love SwiftUI!")
.padding()
}
}
You see that this change gets instantly reflected in the
Modifying views "#
preview simulator. Awesome!

Note: If the preview simulator is not running yet, click on Let's say we want our Text view to be of a larger font. We

the "Resume" button in the upper right corner. Usually, can easily do this by applying a modifier to it. As the name

changes are adopted automatically, but sometimes, says, modifiers are used to modify views in SwiftUI. There

especially when more considerable changes are made, are plenty of them, and by going through this book, you

clicking the Resume button gets necessary. will meet many of them!

You can add a modifier to a view like this:


What are views?
Text("I love SwiftUI!")
.padding()
But what are views? Everything you see in the app is .font(.largeTitle)
somehow built from views. Of course, the ContentView, for
In this example, we choose the .largeTitle option. To see all
example, is a view. You can recognize this not least
of the other options available, you can delete the code
because the ContentView class adapts the View protocol.
inside the braces and just write an ".", which shows you all

But also the Text object that contains our ContentView is available fonts. Feel free to try them out!

itself a view. There are many more such system views, such
as Images, Sliders, Lists, and many more. We can use these
in a kind of modular system to create more complex views.

From this nesting of views into other views, we finally


create higher-level view hierarchies. And the totality of
these view hierarchies finally forms the entire visible
interface of our app.
Okay, we saw how to add modifiers by writing code. But available. Let's say we want to change the color of our Text
the cool thing in SwiftUI is that we can also customize view view. For doing this, search for "Foreground Color". You
using the preview simulator. CMD-click on the Text view in can now drag and drop this modifier onto the Text view.
the simulator and click on "Show SwiftUI Inspector". Here
You see that the Text turns blue, and also the code gets
we can adjust the view, for instance, change the text type.
updated! To choose another color, you can edit the code.
We can also apply new modifiers using the preview To see all available system colors, you can again delete the
simulator. To do this, click on the right "+"-symbol on the code inside the braces and write an ".". But for now, we're
right side of the Xcode toolbar. okay with the blue color.

A menu pops up with different libraries (represented by


the five icons below the search bar). By default, the View
library is shown. Click on the modifier symbol (the second
one from left) to open the Modifier library. You can scroll
through them to get a feeling of which modifiers are
VStack {
Stacking views Text("I love SwiftUI!")
.padding()
.font(.largeTitle)
Next, we want to add another Text view below our current .foregroundColor(.blue)
}
one. For doing this, we have to use so-called stacks. Stacks
Now we can insert another Text view and place it below our
are used in SwiftUI to stack multiple views along a
first one! At this point, also remove the .padding from the
particular axis.
first Text view.
There are three types of Stacks:
VStack {
Text("I love SwiftUI!")
• VStacks: All views wrapped into a VStack get stacked .font(.largeTitle)
.foregroundColor(.blue)
vertically. Text("SwiftUI makes developing iOS apps fun and
easy")
}
• HStack: All views inside a HStack get aligned
Next, we want to limit the width and height of our Text
horizontally.
views. To do this, we can use .frame modifier. By applying it
• ZStack: All views inside a ZStack get stacked on top of to the whole VStack, both Text views are affected as a
each other. whole.

Let's see how Stacks work! Let's say we want to place VStack {
//...
another Text view below our current one. Because both }
.frame(width: 300, height: 150)
Texts should be aligned vertically, we have to use a VStack
for this. By using the .frame modifier, we are positioning the
VStack's elements within an invisible frame.
For embedding our Text view into a VStack, you can simply
CMD-click on it (on the code side) and click on "Embed in
VStack".
At the moment, the Text views are centered. Instead, we VStack(spacing: 50) {
VStack(alignment: .leading, spacing: 10) {
want to align them on the left side. To do this, we can add //...
}
braces behind the VStack and use the alignment argument .frame(width: 300, height: 150)
Button(action: {
to change the alignment mode of the views wrapped into //Show logo
}) {
the VStack. For aligning the views on the left side, we use Text("Show me the logo")
}
the .leading option. We also increase the vertical spacing }
of the Texts by using the “spacing” parameter.
Inside the curly braces, we define the look of the Button. In
VStack(alignment: .leading, spacing: 10) { other words, we determine what view(s) our Button should
//...
} contain. We want our Button to read "Show me the logo!".
.frame(width: 300, height: 150)
Button(action: {
Let's add another view type below our Text views: a Button! //Show logo
}) {
But this time, we try doing it by using the preview
}
simulator. Click on the "+"-symbol, open the view library
(first icon) and search for Button. Now drag and drop it We want the Button's Text view to have a blue background,
below our second Text view (make sure you insert it below a white font, and rounded corners. To do this, we use the
the VStack and not into it). following modifiers:

Text("Show me the logo")


Great, you see that SwiftUI automatically created another .background(Color.blue)
.cornerRadius(10)
VStack and placed the one we created earlier into it. Below .foregroundColor(.white)
this wrapped VStack, we saw our new button view! Again
we want to increase the vertical spacing:
We want to enlarge the Button's background. To add When applying another modifier, in this case,
spacing around individual views, we use the .padding the .cornerRadius modifier, SwiftUI created another new
modifier. view by utilizing the created view (the view created from
the .background modifier) and added a background to it.
Text("Show me the logo")
.background(Color.blue) Applying the modifier (.foregroundColor) used the last
.cornerRadius(10)
.foregroundColor(.white) created view and initialized a new view out of it while
.padding()
changing the Text's font color. And so on!
But wait a moment: Why did the .padding modifier not
affect our Button's Text view? This is because the order of
the modifiers we append to a particular view matters! To
understand this, let's take a look behind the scenes and
explore how modifiers in SwiftUI work.

Why the order of modifiers matters$

Let's take a close look at our Text view. We initialized the


Text view containing the String "Show me the logo!". We
added a background to it by applying the .background
modifier. What happened then is that the modifier utilizes
the Text view and created a new view out of it with And this is why applying the .padding modifier as the last
changing the font type. So the key takeaway to remember one does not work as expected. The .padding modifier of
is that applying a modifier to a view does not really modify the Text view grabs the last created view, a Text view
the view itself but rather create a new, customized view! without blue background and rounded corners.
When we use the .padding modifier as the first modifier,
Conclusion %
the background modifier uses a Text view that already has
some spacing around it and fills it with color! So let's Great, you have learned a lot so far. You now know how to
change our Button's Text view: use views to build the interface for a SwiftUI app. Also, you

Text("Show me the logo")


have already learned two important system views, Text and
.padding() Image views.
.background(Color.blue)
.cornerRadius(10)
.foregroundColor(.white) You also learned how to customize the appearance of
And this is what should happen now: views using modifiers and how to arrange multiple views
using Stacks.

In the next chapter, we will look at the basic data flow


principles in SwiftUI. We will also learn about the @State
property wrapper and how to use it.
What we want to achieve -
When tapping on the “Show me the logo!” Button, we want to
Chapter 4
show the SwiftUI logo above our Text views. By doing this, we are

Data Flow learning the basic data flow concepts used in SwiftUI to react to
external events.

in SwiftUI
• Using Image views in SwiftUI

• Understanding the data flow


concept used in SwiftUI
Before we move on, we first need to import the SwiftUI icon file
• Understanding @State and into our project. To do this, open the Assets.xcassets folder and
@Binding property wrappers
drag and drop the image file into it. Make sure the file is named
correctly.
Using Images in SwiftUI 1  the first modifier applied to the Image view (do you
remember why?).
We want to place the SwiftUI icon right above our two Text
Image("logo")
views.  Therefore, we insert an Image view above our inner .resizable()
.frame(width: 170, height: 170)
VStack.  .clipped()

VStack(spacing: 50) { Next, we apply the .aspectRatio modifier. This modifier


Image("logo")
//... scales the Image so that it fits into the frame by using the
}
specified dimensions. Since our icon is squared anyway, we
When using a plain Image view without any modifiers could skip this step, but applying the .aspectRatio modifier
applied, SwiftUI simply inserts the full-sized Image into is best practice when working with Image views in SwiftUI,
our ContentView's body. Therefore, we need to provide the and we should get used to it. For keeping the original
Image with a frame that serves as a fixed-sized container. dimensions of the Image, we can choose between
We do this by applying the .frame modifier again and the .fill or .fit "contentMode". "Fill" scales the Image while
giving the Image's frame a suitable width and height. For filling out the entire frame with the Image. "Fit" scales the
the Image to not exceed the frame, we have to add Image while ensuring that the whole Image remains visible
a .clipped modifier. The .clipped modifier cuts out the area within the frame. 
of the object that exceeds the specified frame. 

Image("logo")
.frame(width: 170, height: 170)
.clipped()

Next, we need to resize the Image view to fit into the frame
without distorting the dimensions of the original image file.
To be able to scale an Image view, we need to apply
the .resizable modifier first. The .resizable modifier must be
When knowing the required modifiers, its super easy to frame image views in SwiftUI

In our example, we use .fill as the “contentMode”.

Image("logo")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 170, height: 170)
.clipped()
Getting familiar with @State properties 2 
We want to display the SwiftUI icon only after the user We could now change the showIcon value by using the
tapped on the "Show me the logo!" Button. But how can Button's action parameter. But note the following:
we achieve this functionality? To keep track of whether the Whenever the user taps on the Button,
corresponding image view should be shown or not, we the showIcon property would get toggled, though. But this
declare a "showIcon" variable above wouldn't tell the ContentView update its body. This means
our ContentView's body. At default, we don't want to show that the showIcon would get true when the user taps the
the Image view, so we assign false to it. Button, but the ContentView wouldn't notice and,
therefore, wouldn't initialize the Image view by executing
struct ContentView: View {
the code inside the if-statement. 
var showIcon = false

var body: some View { So how can we get the ContentView to rebuild itself when
//...
} the user taps the Button? 
}

For this purpose, Apple provided @State properties within


We only want to show the SwiftUI icon when
SwiftUI. These @-keywords (yes, there are more of them)
the showIcon property is true. Thus, we conditionally
are called property wrappers. Property wrappers in SwiftUI
initialize the Image view by using an if-statement inside the
equip variables with a specific logic depending on the type
outer VStack.
of property wrapper. This logic is the core of the data flow
var body: some View {
VStack(spacing: 50) {
concept in SwiftUI, so understanding property wrappers is
if showIcon {
Image("logo")
really crucial. 
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 170, height: 170)
.clipped()
}
//...
}
}
@State is probably the most frequently used property Let's explain it by looking at our app: Every time the user
wrapper in SwiftUI. We'll get to know most of the other taps the Button, the showIcon property gets toggled.
ones throughout this eBook, but we start with this basic Because this property is a State, its related View,
one.  the ContentView, notices and rebuilds its body with
checking the showIcon property and, based on its value
You can easily declare a State by putting the @State
executing the if-statement with eventually showing/hiding
keyword in front of a variable. Let's do this with
the Image view! 
our showIcon property. 
Try it out in the preview simulator! To use interactive
@State var showIcon = false
elements like buttons in the canvas, you need to tap on the
Hint: State properties must always be related to a view in
"Play" button for starting a Live preview. When you now tap
SwiftUI. So make sure you always declare them inside a
the Button, the showIcon State gets true, which causes the
View struct (but not inside the View's body)! 
whole View to rebuild its body with eventually showing us

We can now toggle the showIcon State when the user taps the SwiftUI icon. If we tap on the Button again,

on the Button. Toggling means that the property gets true the showIcon State gets false again, which causes the

when it's false and vice versa.  ContentView to hide the Image view.

Button(action: { As said, this topic is crucial for mastering SwiftUI, so make


showIcon.toggle()
}) { sure you really understood it! 
//...
}

Again, what does a State property do? Well, you can read
out and manipulate data just as you do with regular
variables in Swift. But the critical difference is that every
time the data of a State changes, the related View gets
rebuilt. 
Pushing views with Spacers ↔
VStack(spacing: 50) {
When showing and hiding the SwiftUI icon, the remaining if showIcon {
Image("logo")
views change their positions. This is because the outer //... }
Spacer()
VStack place all of its elements in the center of the VStack(alignment: .leading, spacing: 10) {
ContentView. Therefore, when the Image view is not //...
}
shown, the remaining views are getting pushed to the //...
}
center. But how could we tell SwiftUI that the Text views
and the Button should always stay on the top, regardless of
whether the Image view is shown.  However, we want our elements to be a bit farther away
from the upper and lower edge. Therefore we add a top
We’re going to use a Spacer view for this.  padding to the Image view ... 

A Spacer creates an invisible space between the contextual if showIcon {


Image("logo")
objects and therefore pushes these objects to the side. A //...
.padding(.top, 180)
Spacer inside a VStack pushes all contained views along }

the xAxis. A Spacer inside an HStack pushes all views along


... and a bottom padding to our Button: 
the yAxis. 
Button(action: {
showIcon.toggle()
The Image view should always be pushed to the top, the }) {
//...
remaining views to the bottom. So let’s insert a Spacer }
between them:  .padding(.bottom, 130)

If we run our app again, the Text views and Button will
always be pushed down to the bottom of the screen (with a
certain padding), regardless of whether the SwiftUI icon is
shown or not. 
Understanding @Binding
Until now, we only worked inside our ContentView struct.
However, when working in SwiftUI, such structs can quickly
get confusing, for example, when using multiple stacking
layers. 

Therefore, it's best practice to outsource views into their


own structs whenever it's possible. So let's say we want to
outsource our Button. To do this, we can CMD-click on it
and click on "Extract subview". Next, name your
outsourced view, for example, "LogoButton". 

We get an error saying, "Value of type 'LogoButton' has no


member "showIcon'". This is because when outsourcing a
view, it gets wrapped into its own struct. But
the LogoButton struct doesn't own such a property. 

To fix this, we need to insert an appropriate property into


our outsourced LogoButton struct. 

But: Instead of creating a State property on its own, we


want to declare a property that derives its content from
the showIcon State of our ContentView to enable data
fl ow between them! 
To do this, we declare a @Binding property that's also
called "showIcon" above our LogoButton's body. Since we
want it to derive its content from the showIcon State of
the ContentView, we don't assign any data to it. Bindings
are used for creating a data connection between a view
(here: our outsourced LogoButton) and its source of data
(here: our showIcon State). 

struct LogoButton: View {

@Binding var showIcon: Bool

var body: some View {


//...
}
}

To create the "link" between our ContentView and


our LogoButton, we initialize the latter by passing
the showIcon State of our ContentView into it! We use the
dollar-sign syntax for binding views. 

LogoButton(showIcon: $showIcon)

Now our outsourced LogoButton is connected to


the showIcon State of our ContentView! 
Conclusion ,

That's it! We finished creating our first SwiftUI app. You


learned many things like using several SwiftUI views like
Texts, Buttons, and Images, and how to stack them. You
also explored how to use States and Bindings.

In the next chapter, we will learn how to present data inside


Lists and how to connect multiple SwiftUI views and stack
them inside a view hierarchy.

You can find the complete source code here.

To learn more about other views like TextField, Pickers,


ScrollViews, Toggles, Sliders, etc. make sure you check out
our Interactive Mastering SwiftUI Book. This eBook
contains 15 chapters full of information about developing
apps with SwiftUI. By going from basic concepts of SwiftUI
over more advanced topics like creating complex user
interfaces to eventually communicating with a backend
server, you will learn everything you need to know about
working with SwiftUI step by step!
What we’ll achieve -
In this chapter, we are going to create a contacts app. By doing
Chapter 5
this, we’ll learn how to present data in rows by using SwiftUI Lists.

Lists and We’ll also learn how to let the user navigate between multiple
views, like you see in the preview below:

Navigation Views
• How to present rows of data
by using Lists 

• How to navigate between


views using a NavigationView
hierarch

• How to cluster views using


Forms and Sections 
Setting up our project 4 Creating a static List ↕
Let's start with creating a new project and importing the A List is used to present multiple rows of data in a single
images we will need later on. Open Xcode 12 and create a column. Let's embed the default "Hello World" text into a
new Xcode project. Then select "App" under the "iOS" tab List to see how it looks like:
and click on Next. Name the project "MyContacts" and
List {
make sure you use "SwiftUI" as the Interface and "SwiftUI Text("Hello, world!")
.padding()
App" as the Life cycle mode. Then click on Create. Our }

Xcode project shows up with presenting the You see that SwiftUI automatically created a row for us.
default ContentView.swift file.  Each view inside a List gets its own row. Currently, that's
only our "Hello World" Text view.
Before we start creating our contacts app, we first need to
import the images we'll use for our contacts' photos. To do
this, open the Assets.xcassets folder and drag and drop the
image files right into it. Make sure the files are named
correctly. 

Great, finished setting up our app's project. So let's dive


into coding up our contacts app! 
Each row should show the contact’s photo, name and phone number

Each row in our List should contain the contact’s photo, Let’s choose the .leading alignment mode for our VStack: 
name, and phone number. Let’s start composing the row’s
VStack(alignment: .leading) {
interface by embedding a Text view into a VStack, //...
}
replacing its String with a sample contact name, and
placing another Text view reading a sample phone number On the left side of the contact’s name and number, we

below it. Make sure you remove the .padding modifier. want to show its photo. To do this, we need to embed the
VStack into an HStack first.
VStack {
Text("Christine Clapper") List {
Text("+1(141)-5115553") HStack {
} VStack(alignment: .leading) {
//...
Let’s highlight the contact name by applying }
}
the .font modifier with the .custom option to the }

corresponding Text view. 


Next, we can insert an Image view above the VStack for
Text("Christine Clapper") creating a sample contact photo. 
.font(.system(size: 21, weight: .medium,
design: .default))
HStack {
Image("christineClapper") Defining the data model '
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 60) Let's create a new Swift file by moving to the Xcode toolbar
.clipped()
.cornerRadius(50) File - New - File and then selecting Swift File. Let's call this
VStack(alignment: .leading) {
//...
file "Contact". Make sure you import the SwiftUI framework.
} For handling our contacts, we create a struct called
}
"Contact" inside this file. 
For keeping our code clean, let's outsource our HStack and
by CMD-Clicking on it, choosing "Extract subview" and import SwiftUI

naming it "ContactRow". struct Contact {

We want to know each Contact's following information: Its


name, phone number, email, its address, and the name of
the corresponding image file inside the assets folder.
Therefore we declare corresponding attributes inside our
struct. 

struct Contact {
let imageName: String
let name: String
let phone: String
let email: String
let address: String
}

Before making our List dynamic, we need to define a data


model for representing the data for each contact inside the
List.
Because we will use our Contact data model to wrap Making our List dynamic 6
multiple instances of it into a List, we need it to conform to
the Identifiable protocol. This is required to pass instances Back to our ContentView: We are now ready to pass our

from a custom data model into Lists in SwiftUI (it's also the contacts data set to our List like this:

reason why we imported the SwiftUI framework into List(contacts) { contact in


ContactRow()
our Contact.swift file). The Identifiable protocol has only }
one mandatory requirement: It requires the struct to
By doing this, our List cycles through our contacts array
contain an attribute to identify every instance by a unique
and creates one ContactRow for every Contact instance
id. Therefore we simply declare an "id" attribute and assign
inside it. Your preview canvas should now look like this:
a UUID instance to it when initializing a Contact.
Using UUID instances is automatically creating unique ids
for us. 

struct Contact: Identifiable {


let id = UUID()
//...
}

Below our Contact struct, we can insert an array for holding


all the contacts for our app. 

Feel free to copy and paste the array from here. Of course,
you can edit this array as you like. 

let contacts = [
//...
]
Of course, not every row should display the same sample If you take a look at your preview canvas you see that the
contact data. Thus, we add a contact property to List successfully cycles through all elements in
our ContactRow struct ...  the contacts array and creates one ContactRow for each by
passing that particular Contact instance to
struct ContactRow: View {
the ContactRow view. 
let contact: Contact

var body: some View {


//...
}
}

... which we initialize with the particular Contact instance in


our ContentView’s List: 

ContactRow(contact: contact)

We can now replace the Strings in our ContactRow’s Text


views with the corresponding properties of the
passed contact. 

HStack {
Image(contact.imageName)
//...
VStack(alignment: .leading) {
Text(contact.name)
//...
Text(contact.phone)
}
}
Composing the DetailView 7
When the user taps on a particular ContactRow, we want to Now we're ready to set up the interface of our DetailView.
present a new view with detailed information about this We start with replacing the default "Hello World" Text view
contact.  with an Image view to display the contact's photo. 

var body: some View {


To do this, we create a new SwiftUI view and name it Image(contact.imageName)
.resizable()
"DetailView". As we did in our ContactRow view, we declare .aspectRatio(contentMode: .fill)
.frame(width: 150, height: 150)
a "contact" property, which will be initialized in .clipped()
our ContentView later on.  .cornerRadius(150)
.shadow(radius: 3)
}
struct DetailView: View {
Next, we wrap the Image view into a VStack and place a
let contact: Contact
Text view below it for displaying the contact's name. 
var body: some View {
//...
} VStack {
} Image(contact.imageName)
//...
Text(contact.name)
However, the corresponding preview struct needs to be .font(.title)
provided with a Contact in order to render the preview .fontWeight(.medium)
}
canvas. For this purpose, we just use the first element of
Below the contact's name, we insert three HStacks for
our contacts array. 
presenting the contact's phone number, email, and its
struct DetailView_Previews: PreviewProvider { address:
static var previews: some View {
DetailView(contact: contacts[0])
}
}
VStack {
//...
HStack {
Text("Phone")
Spacer()
Text(contact.phone)
.foregroundColor(.gray)
.font(.callout)
}
HStack {
Text("Email")
Spacer()
Text(contact.email)
.foregroundColor(.gray)
.font(.callout)
}
HStack {
Text("Address")
Spacer()
Text(contact.address)
.foregroundColor(.gray)
.font(.callout)
.frame(width: 180)
}
}

Honestly, that doesn’t look very good. Let’s change this by


wrapping the three HStacks into a so-called Form.

Form {
HStack {
//...
}
HStack {
//...
}
HStack {
//...
}
}
Looks much better now! But what did the Form actually do The preview of the DetailedView should look like this: 
for us? A Form groups views in a way you know, for
example, from the iOS system’s settings app. The cool
thing about Forms is that SwiftUI automatically adapts the
layout and appearance of the views wrapped into the Form
for us! Let’s say we want to add two views two our row: one
Button for texting the contact and one for calling him. But
we want those two views to be separated from the other
views inside the Form. To do this, we wrap those into
Sections like this: 

Form {
//...
Section {
Button(action: {
print("Send message")
}) {
Text("Send message")
}
Button(action: {
print("Call")
}) {
Text("Call")
}
}
} Now it’s time to link it to the ContentView so that when the
user taps on a particular ContactRow it opens
Great, we’re already finished with composing the interface the DetailView with showing the detailed information
of our DetailView.  about the selected contact. 
Stacking views using NavigationViews
For being able to navigate to the
corresponding DetailView when we tap on a
certain ContactRow, we need to embed
our ContentView's List into a NavigationView like this: 

NavigationView {
List(contacts) { contact in
ContactRow(contact: contact)
}
}

However, our ContentView doesn't have a navigation bar


yet. To change this, we append
the .navigationTitle modifier to our List. 

NavigationView {
List(contacts) { contact in
ContactRow(contact: contact)
}
.navigationTitle("MyContacts")
}
To navigate to the DetailView when tapping on a row, we
SwiftUI appends a large navigation bar by default. If you wrap each ContactRow instance that gets created inside
want to use the smaller one, you can use the the List into a NavigationLink while using the
"displayMode" argument and choose the .inline option. particular DetailView instance that instance as the
But for now, we are fine with the default navigation bar NavigationLink's "destination". 
style. 
NavigationLink(destination: DetailView(contact:
contact)) {
ContactRow(contact: contact)
}
The NavigationLink tells SwiftUI to push Awesome, by using our NavigationView we stack
the DetailView equipped with the passed Contact instance our ContentView and our DetailView(s) in a navigation
on top of the navigation hierarchy. We can see how this hierarchy. By tapping on the ContactRow the user can
looks like by running our app in the Live preview.  navigate to the DetailView. By using the navigation bar, the
user can navigate back to the ContentView.
Conclusion ,

That's it! We finished creating our own "MyContacts" app.


We learned how to present data in SwiftUI Lists and how to
cluster views inside forms. We also learned how to navigate
between multiple views by using a navigation view
hierarchy.

You can find the complete source code here.

Maybe you're asking yourself how you can navigate


between views independently by not using a navigation
view stack. Then take a look at this tutorial!

Or you check out our Interactive Mastering SwiftUI Book


where we cover this topic as well. This eBook contains 15
chapters full of information about developing apps with
SwiftUI. By going from basic concepts of SwiftUI over more
advanced topics like creating complex user interfaces to
eventually communicating with a backend server, you will
learn everything you need to know about working with
SwiftUI step by step!
What we’ll achieve -
In this chapter, we're going to create a timer app. By doing this,
Chapter 6
we'll practice composing more complex and dynamic interfaces

Timer App and how to use @ObservableObjects as your view's model. The
timer app will look like this:

• Creating flexible and dynamic


interfaces

• Using system icons in SwiftUI

• Understanding and using


ObservableObjects and
@StateObjects

• Storing small chunks of data


using UserDefaults

Let's get started by creating a new Xcode 12 project, choosing


"App" under "iOS" and naming it "MyTimer".
As you saw above, the app's interface should depend on Composing the Timer’s UI /
whether the timer has not been started, is running, or is
paused. We'll often refer to these three different scenarios, We start by wrapping the "Hello World" Text view into a

so it's useful to begin by creating a corresponding enum NavigationView and removing the .padding modifier. Then,

for representing these scenarios. To do this, create a new we wrap the Text into a VStack and append

Swift file and call it "Helper". Inside this file, insert the a .navigationTitle to it. 

following enum: NavigationView {


VStack {
enum TimerMode { Text("Hello, world!")
case running }
case paused .navigationTitle("MyTimer")
case initial }
}
Instead of "Hello World" the Text view should display the
For keeping track of the current TimerMode, we declare a
time remaining. For now, we use the temporary and static
corresponding State property inside our ContentView to
value 60 for representing that 60 seconds are left before
adapt the app's interface whenever the timer gets started/
the timer runs off. We highlight the Text view by increasing
paused/ stopped.
its size and append some top padding to it. 
struct ContentView: View {
Text("60")
@State var timerMode: TimerMode = .initial .font(.system(size: 80))
.padding(.top, 80)
var body: some View {
Text("Hello, world!") Below our Text view, we want to place a large play icon to
.padding()
} start the timer. At this point, we could use Image views with
}
initializing custom icons we would have had to import into
Now we’re ready to start composing our ContentView the Assets folder before, but fortunately, there is a much
depending on the current timerMode. easier way: Using system symbols!
Apple provides us with a bunch of integrated icons we can
use for creating our UI. We can initialize such an icon by
using an Image view with the system argument. Therefore
we add a Image view below our Text view:

Image(systemName: "play.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 180)
.foregroundColor(.orange)

Working with SF Symbols 8


At this point, you are probably asking yourself: How am I
supposed to know what system icons are available and
how to find their names so I can use them within my app? 

Well, that's super easy! Just use the free Apple SF symbols
app. This macOS app provides you with a set of over
1,500 symbols you can use in your app. 

You can download the app here. 

Once you open SF symbols, you can scroll through the


symbols, or you search for a particular term to find
suitable symbols for your purpose. The app then lists
different icons we can use by using their names as the
Image's "systemName" argument! 
However, the Image view should only display to play icon Accepting user input with Pickers
when the timer is in its initial State. While the timer is
running, we want to present a pause icon. Therefore, we A Picker is a piece of UI that includes one or multiple lists

utilize the "systemName" parameter like this: displayed as a wheel. The user can use this wheel(s) for
selecting a specific value.
Image(systemName: timerMode == .running ?
"pause.circle.fill" : “play.circle.fill")
Pickers are often found within iOS apps. Take a look at the
Now, while the timer is running, the Image view shows the date selection in the iOS system’s Calendar app or the time
pause icon we can find in the SF symbols app. Otherwise, it selection in the iOS Clock app.
shows the play icon.
To use a Picker inside for our timer app, we first need to
When the timer is paused, we want to display a small create a State property for keeping track of the current
rewind icon so that the user can reset the timer. To do this, selection.
we insert the following code below our current Image
@State var selectedPickerIndex = 0
view:
Next, we create the data set for our Picker. In our case, it‘s
if timerMode == .paused {
Image(systemName: "gobackward") an array holding the values 1 to 59 (minutes). Insert this
.resizable()
.aspectRatio(contentMode: .fit) property below your States.
.frame(width: 50, height: 50)
.padding(.top, 40) let availableMinutes = Array(1...59)
}
Now we can create a Picker with binding it to the State. We
While the timer is in its initial State we want to provide the
want no label, so just insert a Text with an empty String and
user with a possibility to set the desired timer length. For
apply the .labelIsHidden modifier to it.
this purpose, we're going to use a Picker.
Picker(selection: $selectedPickerIndex, label:
Text("")) {

}
.labelsHidden()

We embed a ForEach loop into the Picker that cycles


through every element in our availableMinutes array. We
use the loop's closure for creating one Text view for every
minute and assign it to an index.⠀ 

Picker(selection: $selectedPickerIndex, label:


Text("")) {
ForEach(0 ..< availableMinutes.count) {
Text("\(self.availableMinutes[$0]) min")
}
}

SwiftUI then knows which index every view should have


and updates the State depending on the current selection!  You can check if the interface behaves correctly by
changing the "timerMode" State and looking at the
Finally, we push everything to the top by using a Spacer as
preview canvas. When you change
the last view inside the VStack. 
the timerMode to .running, the Picker should disappear,
VStack {
//...
and the play icon should be replaced with a pause icon.
Spacer() When you change it to .paused, the rewind icon should
}
show up. 
If you start a Live preview, you can scroll through the Picker.
We'll use this functionality later on the set the timer length.  Great, we're done with implementing the UI for our timer
app. Let's get started with actually implementing the timer
functionality! 
Working with ObservableObjects  Let's create such an ObservableObject by creating a new
Swift file and calling it "TimerManager". Make sure you
You already learned what @State and @Bindings are and import the SwiftUI framework. Next, insert a class called
what they are used for. For implementing our timer TimerManager as well that adopts
functionality, we'll use a so-called ObservableObject.  the ObservableObject protocol: 

ObservableObjects are similar to State properties but with import SwiftUI

some differences.  class TimerManager: ObservableObject {

}
● OberservableObjects are a protocol a class can conform
to, not a property wrapper Our TimerManager should keep track of whether the timer
is running, paused, or in its initial State. Therefore declare a
● These classes can contain data, for example, a String  variable called "timerMode". 

assigned to a variable  class TimerManager: ObservableObject {

var timerMode: TimerMode = .initial


● We can bind one or multiple views to the
}
ObservableObject (or better said, we can make these
views observe the object).  Back to our ContentView: To make it observe
the TimerManager, we have to initialize a @StateObject
● The observing views can access and manipulate the data inside it. 
inside the ObservableObject. When certain properties
@StateObject var timerManager = TimerManager()
inside the ObservableObject are updated, all observing
views re-render their bodies, similar to when a State We want our ContentView's body to refer to
updated the timerManager's timerMode property instead of
the ContentView's State. 
Don't worry if you don't understand all of this. Everything
should become clear as you continue reading.
VStack {
//... Implementing a Swift timer ⏲
Image(systemName: timerManager.timerMode
== .running ? "pause.circle.fill" :
"play.circle.fill") Implementing a timer itself is really easy in Swift. We just
//...
if timerManager.timerMode == .paused { need to add a Timer instance to our TimerManager
//...
} var timer = Timer()
if timerManager.timerMode == .initial {
//...
} Next, we add a function called start to our class that
Spacer()
}
triggers the timer. We’ll execute this function when the user
taps on the play icon. When the function gets called, we
Because we won’t need it anymore, we can delete
want to change the timerMode to .running. 
the ContentView’s timerMode State property 
func start() {
Next, our TimerManager needs another property for timerMode = .running
}
keeping track of the remaining seconds. 
Then we tell our timer to decrease the secondsLeft every
var secondsLeft = 60 second until it hits zero. 
We want the Text view in our ContentView to refer to that
timer = Timer.scheduledTimer(withTimeInterval: 1.0,
value instead of using the static one. Therefore we update repeats: true, block: { timer in
if self.secondsLeft == 0 {
it accordingly: self.timerMode = .initial
self.secondsLeft = 60
timer.invalidate()
Text("\(timerManager.secondsLeft)") }
self.secondsLeft -= 1
})

As said, we want this function to be called when we tap on


the play icon. We could do this by wrapping the
corresponding Image view into a Button, but there’s a
shorter alternative. 
We can achieve the same functionality by simply adding a the timerMode as well so that our ContentView reverts to its
tap gesture to our Image view that notices when the user initial look when the secondsLeft are hitting zero.
touches the Image view.
@Published var timerMode: TimerMode = .initial

Image(systemName: timerManager.timerMode == .running ?


"pause.circle.fill" : "play.circle.fill")
If now we run our app and tap on the play icon, our timer
//... starts decreasing every second. When it hits zero, the
.onTapGesture(perform: {
timerManager.start() interface reverts to its default look. 
})

Let’s check if our timer works by running our app in a Live


preview and tapping on the play icon. Odd, the time is not
running down. Why that?

How to update observing views 6


Until now, changes in the secondsLeft property don’t cause
the observing ContentView to refresh. To tell SwiftUI that all
observing views should get updated whenever
the secondsLeft is decreasing, we have to use the
@Published property wrapper. 

@Published var secondsLeft = 60

Every time the secondsLeft gets now updated, the


observing ContentView will rebuild its body with eventually
showing us the remaining time after tapping on the play
icon. At the same time, let’s use the @Published property or
Pausing and resetting the timer ⏸⏮ We call the reset function by appending the corresponding
tap gesture to the Image view that shows to rewind
We already have a function for starting the timer but we still symbol. 
need one for pausing it and resetting it. Therefore, add the
Image(systemName: "gobackward")
following two functions to your TimerManager class:  //...
.onTapGesture(perform: {
func reset() { timerManager.reset()
self.timerMode = .initial })
self.secondsLeft = 60
timer.invalidate() Great, if we run our app now, we’re able to pause and reset
}
our timer! 
func pause() {
self.timerMode = .paused

}
timer.invalidate() Setting the timer length by using
UserDefaults
You maybe noticed that the code inside the reset function
is the same that we execute in our start function when We’re already able to start, pause, and stop our timer.
the secondsLeft hit zero. To refactor our code, we simply However, we can’t set the timer length by using the Picker
call the reset function from our start function.  yet.

if self.secondsLeft == 0 {
self.reset()
To store the chosen time persistently, we’re going the use
} the UserDefaults. UserDefaults can be used to store small
In our ContentView, we want to call the pause function chunks of data, such as the timer length setting.
when the user taps on the pause icon. To achieve this, we
update our .onTapGesture modifier as follows: 

.onTapGesture(perform: {
timerManager.timerMode == .running ?
timerManager.pause() : timerManager.start()
})
For this purpose, we add a function called @Published var secondsLeft =
UserDefaults.standard.integer(forKey:
"setTimerLength" to our TimerManager. The function “timerLength")

should accept the desired time length as an Integer:  We want to retrieve to timer length from

func setTimerLength(minutes: Int) {


the UserDefaults inside our reset function as well: 

} func reset() {
//...
We can access the UserDefaults like this:  self.secondsLeft =
UserDefaults.standard.integer(forKey:
"timerLength")
func setTimerLength(minutes: Int) { //...
let defaults = UserDefaults.standard }
}
We want to call the setTimerLength function whenever the
The structure of the UserDefaults is very similar to a
user is starting the timer. Therefore, we call
dictionary. We can store pieces of data under an individual
the setTimerLength function from the ContentView's Play/
key and retrieve it again using the specified key. When we
Pause Image view but only when the timerMode is .initial.
call the setTimerLength function, we want to store the given
For the minutes parameter, we use the selected minutes
minutes under a key we call "timerLength". Finally, we
from our Picker. 
assign the published secondsLeft property to this value
when the function gets called.  .onTapGesture(perform: {
if timerManager.timerMode == .initial {

func setTimerLength(minutes: Int) { timerManager.setTimerLength(minutes:


let defaults = UserDefaults.standard availableMinutes[selectedPickerIndex]*60)
defaults.setValue(minutes, forKey: "timerLength") }
secondsLeft = minutes //...
} })

The secondsLeft property is still 60 when


the TimerManager gets initialized. Instead, we want to
retrieve the stored value from the specified "timerLength"
key: 
Converting seconds to a timestamp 
If we run our app now, we can set the desired timer length
by using the Picker. However, when selecting, for instance,
12 minutes, our app shows us the plain seconds left.
Instead, we want to display a timestamp for better
readability. To achieve this, add this function to
your Helper.swift file. 

func secondsToMinutesAndSeconds (seconds : Int) ->


String {
let minutes = "\((seconds % 3600) / 60)"
let seconds = "\((seconds % 3600) % 60)"
let minuteStamp = minutes.count > 1 ? minutes :
"0" + minutes
let secondStamp = seconds.count > 1 ? seconds :
"0" + seconds
return "\(minuteStamp):\(secondStamp)"
}

In our ContentView, we can now use this function to


convert the secondsLeft Integer into a corresponding
timestamp String. 

Text(secondsToMinutesAndSeconds(seconds:
timerManager.secondsLeft))
//…

If we start our timer now, we see that the secondsLeft get


converted into a corresponding timestamp String! 
Conclusion ,

That's it! We finished creating our timer app. We learned


how to create more complex and dynamic interfaces with
SwiftUI. We also explored @ObservableObjects and how
we can use them for communicating with our view's
model. 

Additionally, we learned how we can use Swift timers and


how to store small chunks of data persistently by using
UserDefaults! 

You can find the full source code of the app here. 


Congratulations, you just finished this book!
That’s it for now. Thank you for reading this book! You just got
Chapter 7
familiar with the basics of SwiftUI. You learned how to compose

Where to go interfaces step-by-step by using SwiftUI views. You learned how


data flow works in SwiftUI and know what @State, @Bindings, and
ObservableObjects are. You also explored how to present data by

from here using Lists and how to navigate between views in a view hierarchy.

If you want to learn more about SwiftUI, visit BLCKBIRDS.COM for


more free SwiftUI tutorials!

Want to dive deeper into SwiftUI? Then check out our Interactive
Mastering SwiftUI Book. In 15 chapters, we’re creating multiple
useful apps (for example, a CoreData To-Do app, Chat Messenger,
and a realtime Stocks App) and learn a lot more about SwiftUI.
With lifetime-updates and full code support, we teach you how to
develop your own iOS apps using SwiftUI!

If you have any wishes or suggestions on what you would like to


learn, make sure you send us a mail or leave a DM on Instagram.

For inspiration, more content about SwiftUI development, and tips


for SwiftUI developers, follow us on Instagram and Twitter.

You might also like