SwiftUI Basics 2.0
SwiftUI Basics 2.0
THE
STUDY
OF CELL
BIOLOGY
Introduction to the building
blocks of life
Copyright © 2020 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 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.
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.
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.
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.
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.
Excursus:
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
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!
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.
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:
Data Flow learning the basic data flow concepts used in SwiftUI to react to
external events.
in SwiftUI
• Using Image views in SwiftUI
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
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?
}
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.
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 ...
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.
LogoButton(showIcon: $showIcon)
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
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.
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 }
struct Contact {
let imageName: String
let name: String
let phone: String
let email: String
let address: String
}
from a custom data model into Lists in SwiftUI (it's also the contacts data set to our List like this:
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
ContactRow(contact: 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.
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)
}
}
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 ,
Timer App and how to use @ObservableObjects as your view's model. The
timer app will look like this:
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.
Image(systemName: "play.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 180)
.foregroundColor(.orange)
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.
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()
}
● 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".
}
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 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 {
Text(secondsToMinutesAndSeconds(seconds:
timerManager.secondsLeft))
//…
from here using Lists and how to navigate between views in a view hierarchy.
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!