Chapters

Hide chapters

iOS Apprentice

Eighth Edition · iOS 13 · Swift 5.2 · Xcode 11

My Locations

Section 4: 11 chapters
Show chapters Hide chapters

Store Search

Section 5: 13 chapters
Show chapters Hide chapters

2. Getting Started with SwiftUI
Written by Joey deVilla

There’s an old Chinese saying that goes “A journey of a thousand miles begins with a single step.” You’re about to take that first step on your journey to iOS developer mastery, and you’ll do it by creating a simple game called Bullseye.

This chapter covers the following:

  • SwiftKit and UIKit: These are two ways to build apps and user interfaces, and you’ll learn both.
  • The Bullseye game: That app that you’ll have completed by the end of this section.
  • Getting started: Enough preamble — let’s create a new project!
  • Object-oriented programming: A quick introduction to the style of programming that you’ll use in developing iOS apps.
  • Adding interactivity: An app that just sits there is no fun. Let’s make it respond to the user!
  • State and SwiftUI: What is “state,” and what does it have to do with SwiftUI?
  • Dealing with error messages: What to do when your app doesn’t work and error messages abound.
  • The anatomy of an app: A brief explanation of the inner workings of an app.

SwiftUI and UIKit

There’s another saying (erroneously) attributed to the Chinese: “May you live in interesting times.” Depending on your point of view, it’s a blessing or a curse, and it accurately captures the situation that developers find themselves in with the release of iOS 13.

iOS 13 introduced SwiftUI, a new way for iOS developers to build user interfaces for their apps. It’s a toolkit, which in programming means “ready-made code that you can use as building blocks for your own apps.” Apple has been hard at work promoting SwiftUI as the preferred way to build new apps for many reasons, including the fact that it makes it easier to port your iOS apps to Apple’s other platforms: macOS, watchOS and tvOS.

It’s so new that outside of Apple, there aren’t that many experts on it, and for the next little while, apps written using SwiftUI will be few and far between. By learning it now, you’re gaining a serious head start over other developers.

UIKit is SwiftUI’s long-standing predecessor. It’s been around since iOS 2.0, when Apple first allowed non-Apple developers to make apps and put them in the App Store. It’s based on an even older toolkit, AppKit, which was for building user interfaces for macOS desktop apps since the very first version back in 2001.

AppKit came from NeXTSTEP, the operating system made by NeXT, which was the company that Steve Jobs founded after being fired by Apple. Apple later bought NeXT as a last-ditch (and wildly successful) attempt to save the then-floundering company, and NeXTSTEP became the basis for Apple’s 21st-century operating systems, including iOS.

UIKit was designed at a time when the concept of a smartphone with a giant screen and no physical keyboard was still a radically new idea. Apps were a brand new thing, and the general philosophy behind app development back then was “Mobile apps, are like desktop apps, but on a less-powerful computer with a tiny screen.” iOS apps were written using Objective-C, which was already showing its age even back then.

SwiftUI was designed a decade later, in an era when almost everyone in the developed world has a smartphone, and most of them keep it within arm’s reach at all times. Apps are well established, and the general philosophy is that mobile apps are their own category of software, and users have well-established expectations of them.

The preferred language for writing iOS apps is now Swift. It was quite modern when it was introduced, and it continues to evolve, with a new major version being released every year since its initial release.

Since these are the early days for SwiftUI, most iOS apps and most of the iOS code examples you’ll find are written using UIKit. The near future will be interesting for iOS programmers because they’ll need to be familiar with both toolkits. That’s why this book covers SwiftUI and UIKit.

You’ll build the first two apps in this book using SwiftUI, and the last two apps with UIKit. Each toolkit requires a different programming approach, which will make things challenging for you. We also hope that learning both will be rewarding and fun!

The Bullseye game

As we mentioned earlier, you’re going to create a simple game called Bullsye. Here’s what it’ll look like when you’re finished:

The finished Bullseye game
The finished Bullseye game

The objective of the game is to put the bullseye as close to the target as you can. The bullseye is on a slider that goes from 1 to 100, and the target value is randomly chosen. In the screenshot above, you’re challenged to put the bullseye at 40. Since you can’t see the current value of the slider and there aren’t any markings to help you, you have to “eyeball” it.

When you’re confident of your estimate, you press the Hit Me! button and a pop-up will tell you what your score is:

An alert pop-up shows the score
An alert pop-up shows the score

The closer to the target value you are, the more points you score. After you dismiss the alert pop-up by pressing the OK button, a new round begins with a new random target. The game repeats until the player presses the Start Over button (the one near the bottom-left corner), which resets the score to 0 and the round to 1.

This game probably won’t make you an instant millionaire on the App Store, but it will show you the basics of making an app and building user interfaces with SwiftUI. Hey, even future millionaires have to start somewhere!

Making a programming to-do list

Now that you’ve seen what the game should look like, and what the gameplay rules are, make a list of all the things that you think you’ll need to do in order to build this game. It’s okay if you draw a blank, but try it anyway.

Here’s an example:

The app needs to put the “Hit Me!” button on the screen and show an alert pop-up when the user presses it.

Try to think of other things the app needs to do. It doesn’t matter if you don’t actually know how to accomplish these tasks. The first step is to figure out what you need to do. How to do these things isn’t important yet.

Once you know what you want, you can also figure out how to do it, even if you have to ask someone or look it up. But the what comes first.

You’d be surprised at how many people start writing code without a clear idea of what they’re actually trying to achieve. No wonder they get stuck! Whenever you start working on a new app, it’s a good idea to make a list of all the different pieces of functionality you think the app will need. This will become your programming to-do list. Having a list that breaks up a design into several smaller steps is a great way to deal with the complexity of a project.

Note: If you ever need a fancy way of saying that you’re breaking down a big complex task into a set of smaller, simpler tasks, just say that you’re performing functional decomposition.

You may have a cool idea for an app, but when you sit down to write the program it can seem overwhelming. There is so much to do, and there’s always the question: “Where do I begin?” By cutting up the project into small steps, you make it less daunting. You may find that some of those small steps can be divided into even smaller steps. You can always find a step that is simple and small enough to make a good starting point and take it from there.

Don’t worry if you find this exercise challenging. You’re new to all of this! As you gain more programming experience, you’ll find it easier to identify the different parts that make up a design and become better at spliting it into manageable pieces.

Here’s an example of a to-do list based on the description of Bullseye:

  • Put a button on the screen and label it “Hit Me!”

  • When the player presses the “Hit Me!” button, the app has to show a pop-up that shows the player a score indicating how close they were to the target.

  • Put text on the screen, such as “Score:” and “Round:”. The score increases as the player earns more points, and the number of rounds increases with each attempt by the player.

  • Put a slider on the screen with a range between the values 1 and 100. The player moves the slider as closely to the target value as they can.

  • Come up with the target value at the start of each round and display it on the screen. This needs to be a random number between 1 and 100, inclusive.

  • Determine the value of the slider (based on its position) after the player presses the “Hit Me!” button.

  • Compare the value of the slider to the target value and calculate a score based on how far off the player is. Show this score in the alert pop-up.

  • Put a “Start Over” button on the screen. Make it reset the score to zero and round to one.

  • Put the app in landscape orientation.

  • Make it look pretty.

There may be a detail or two missing from this list, but it’s a good starting point. Even for a game as basic as this one, there are quite a few things you need to do. Making apps is fun, but it’s definitely a lot of work, too!

Getting started

The first two items on the Bullseye to-do list are, essentially:

  1. Put a button on the screen.
  2. Show a pop-up when the player presses the button.

You’ll start by building an app that does only these two things. Once you’ve done this, you’ll build the rest of Bullseye on this foundation.

This initial app will look like this:

The app contains a line of text and a single button (left) that shows an alert when pressed (right)
The app contains a line of text and a single button (left) that shows an alert when pressed (right)

Time to start coding! To follow the coding exercises in this book, you’ll need:

  • Coding tools. The exercises in this book require Xcode 11.0 or later, which you can download for free using the App Store on your Mac. Xcode 11 requires macOS 10.15, also known as “Catalina”. It won’t run on prior versions of macOS. The differences between Xcode 11 and earlier versions are so big that we don’t recommend using an earlier version.

  • Optionally — but ideally — a device. The apps you’ll make will run on an iPhone running iOS 13.0 or later, or an iPad running iPadOS 13.0 or later. If you don’t have a device, you can make do with the Simulator, an application that runs on your Mac and acts as if it were an iPhone, iPad, Apple Watch, or Apple TV. It allows you to test the apps you write without having to deploy it to a device. You can do the exercises in this book without a device with a few limitations, but there’s no substitute for the real thing.

Creating a new project

➤ Launch Xcode. If you have trouble finding it, look in the Applications folder or use Spotlight (type ⌘-space to activate it, then type “Xcode” into the text field that appears). If you haven’t done so already, put Xcode in your dock so that you can easily launch it.

You’ll see the “Welcome to Xcode” window when it starts:

Xcode bids you welcome
Xcode bids you welcome

➤ Choose Create a new Xcode project. The main Xcode window appears with an assistant that lets you choose a template:

Choosing the template for the new project
Choosing the template for the new project

Xcode comes with templates for a variety of app styles, each of which is pre-configured with code for a different kind of application. When you choose one of these templates, Xcode creates a new project that includes the source files — files containing the code that make up an app — that are necessary to create the kind of app you selected. These templates are handy because they’re ready-made starting points that can save you a lot of effort.

➤ Select Single View App and click Next.

Single View App is the the simplest of the iOS app templates. It’s a simple app with a single view — which is the term we tend to use for “screen” or “page” — that displays the text “Hello World”. You’ll use this as the basis for Bullseye.

You’ll be shown a pop-up where you enter options for your new project.

Configuring the new project
Configuring the new project

➤ Fill out these options as follows:

  • Product Name: Enter Bullseye here.

  • Team: If you’re already a member of the Apple Developer Program, this will show your team name. For now, it’s best to leave this set to None. We’ll cover this in more detail later on.

  • Organization Name: Put your own name or the name of your company here.

  • Organization Identifier: You should fill this with something that uniquely identifies you or your organization. The standard practice is to enter your personal or organization domain name in reverse here. For example, if your domain name is mydomain.com, enter com.mydomain into this field. If you don’t have your own domain name, enter com.example. Don’t worry too much about what you enter here right now, it doesn’t really matter until you submit your app to the App Store, and you can always change this setting later.

  • Language: Make sure that this is set to Swift. All the exercises in this book are in Swift.

  • Use SwiftUI: Make sure that this is selected. You’ll use SwiftUI to create the user interface for Bullseye.

  • Use Core Data, Include Unit Tests, and Include UI Tests: Make sure that these are not selected. You won’t use any of these features in this project.

➤ Press Next. Now, Xcode will ask where to save your project:

Choosing where to save the project
Choosing where to save the project

➤ Choose a location for the project files. For example, the Desktop or your Documents folder.

Xcode will automatically make a new folder for the project using the Product Name that you entered in the previous step, Bullseye in this case, so you don’t need to make a new folder yourself.

At the bottom of the File Save dialog, there is a checkbox labeled Create Git repository on My Mac. You can ignore this for now. You’ll learn about the Git version control system later on.

➤ Click Create to finish.

Xcode will now create a new project named “Bullseye,” based on the Single View Application template, in the folder you specified. When it is done, the screen should look something like this:

The main Xcode window at the start of your project
The main Xcode window at the start of your project

There may be small differences between the screenshot above and what you see on your own computer. As long as you’re running Xcode version 11.0 or later, any differences you see should only be superficial.

Important: Before you continue, examine the list of files on the left side of the Xcode window. If you see a file named ContentView.swift, your project is set up properly and you can proceed to the next step. If you don’t see a file named ContentView.swift in the list, but instead see ViewController.swift, it means that you forgot to check the UseSwiftUI checkbox when choosing the options for the project. If you see files with the names ViewController.h and ViewController.m, then you picked the wrong language (Objective-C) when you created the project. In either case, start over and be sure to check the UseSwiftUI checkbox and choose Swift as the programming language.

Now, let’s take a closer look at your project.

Looking at the Editor

The first thing you should look at is the Editor, which takes up most of the left side of the Xcode window:

The Editor in a newly created Single View Application project
The Editor in a newly created Single View Application project

You’ll spend a lot of time in the Editor, as it’s where you enter — and as its name implies; edit — code. Right now, it’s displaying the source code inside the ContentView.swift file. This file defines what goes into and what happens on the app’s single “screen” or “page,” which is also referred to in programming terms as a view.

For now, you should concern yourself with the part of the code that actually determines what the app does. It’s the middle section, shown below:

import SwiftUI

struct ContentView : View {
  var body: some View {
    Text("Hello World")
  }
}

Don’t worry if this makes no sense to you right now, we’ll review this line-by-line later in this chapter. However, here’s a quick sneak peek if you’re especially curious:

The first line, import SwiftUI, is an instruction to make use of the SwiftUI toolkit. SwiftUI provides a lot of features that you can call on to make user interfaces and respond to user actions.

The rest of the code defines the app’s single view (remember, in this case, when we say “view”, you should think “screen” or “page”). It says that there is a thing called ContentView and this it’s a View. It also says that ContentView contains a single Text object that displays the text “Hello World”.

Note that the source code above leaves out the part at the beginning: The handful of bluish-gray lines of text ending with a copyright notice. This is a block of comments, which are notes intended for people who will read the code. They have no effect on the app or how it runs. We’ll discuss comments in more detail in the following chapter.

The source code above also leaves out the part at the end that starts with the line #if DEBUG. This code is responsible for drawing a preview of your app, and we’ll play around with it later.

Looking at the Canvas

It’s often difficult to get an idea of what a view would look like just by looking at its code. That’s what the Canvas is for. It’s located just to the right of the Editor and looks like this:

The Canvas pane at the start
The Canvas pane at the start

The Canvas pane shows the message Automatic preview updating paused in its upper left corner. This means that it’s currently not updating its contents to show the visual results of the code in the Editor. Let’s un-pause it so that you can see what the view should look like.

Note: If you don’t see the Canvas in the Xcode window, click the Editor Options button. A menu will appear; select Canvas:

The Editor Options button and menu
The Editor Options button and menu

➤ Click Resume (in the upper right corner of the Canvas pane) to see a preview of the app’s view.

A spinning progress indicator will appear in the Canvas pane and, after a few moments, you should see this:

The Canvas pane after pressing the Resume button
The Canvas pane after pressing the Resume button

The canvas is a much easier way to visualize your user interface, especially as your views become more complicated. As you’ll learn later, you can edit your user interfaces in the canvas too!

Running your project

Now, let’s bring your project to life by running it in the Simulator.

Once again, the Simulator is a macOS application that pretends to be various Apple devices: iPhones, iPads, Apple Watches, and Apple TVs. It’s useful for running quick tests of your apps and for trying it out on devices that you don’t have. In the beginning, you’ll run your apps on the Simulator. Later on, you’ll learn how to deploy them to a device.

➤ Click on the device picker near the top-left corner of the Xcode window:

The device picker
The device picker

➤ In the menu that appears, under the section marked iOS Simulators, select iPhone XR:

The device picker menu with iPhone XR selected
The device picker menu with iPhone XR selected

➤ Click the Run button near the top-left corner of the Xcode window, to the left of the device picker:

Press Run to launch the app
Press Run to launch the app

Note: If this is the first time you’re using Xcode, it may ask you to enable developer mode. Click Enable and enter your password to allow Xcode to make these changes. Also, make sure that you do not have your iPhone or iPad plugged into your computer at this point. Otherwise, Xcode might try to run the app on the actual device instead of the Simulator. Since you’re not yet set up for running on a device, this could result in errors that might leave you scratching your head. Stick with the Simulator for now; you’ll learn how to deploy the app to your phone later.

Xcode will labor for a bit, and will eventually launch your brand new app in the Simulator. The app doesn’t look like much — and there’s not much you can with it, either. That said, it’s an actual running app, and an important first milestone in your journey!

What an app based on the Single View Application template looks like
What an app based on the Single View Application template looks like

If the app doesn’t run and Xcode says Build Failed or A build only device cannot be used to run this target when you click the Run button, make sure that iPhone XR (or any other iPhone model listed under iOS Simulators in the device picker’s menu) — not Generic iOS Device — is selected in the device picker:

Making Xcode run the app on the Simulator
Making Xcode run the app on the Simulator

Until you press Stop, Xcode’s Activity viewer at the top says, “Running Bullseye on iPhone XR”:

The Xcode activity viewer
The Xcode activity viewer

➤ Click the Stop button to exit the app:

Press the stop button to stop the app
Press the stop button to stop the app

On your phone, or in the Simulator, you’d use the Home button to exit an app. On the Simulator, you could also use the HardwareHome item from the menu bar or use the handy ++H shortcut), but that won’t actually terminate the app. It will disappear from the Simulator’s screen, but the app stays suspended in the Simulator’s memory, just as it would on a real iPhone.

It’s not really necessary to stop the app. You can go back to Xcode and make changes to the source code while the app is still running. However, these changes won’t become active until you click Run again. This will terminate any running version of the app, build a new version and launch it in the Simulator.

What happens when you click Run?:

Xcode will first compile your source code — that is, translate it from Swift into executable binary code. Languages like Swift, which are called high-level languages, are for human programmers, who are better at things like creativity and the overall design and logic of the application that they’re creating. Executable binary code — also called machine code — is a low-level language; it’s for processor chips like the one in the iPhone (and the simulated one in the Simulator), which are better at things like performing up to trillions of math calculations in a second. As you might expect, human and machine languages are quite different, and so a translation step, called compilation, is necessary.

The compiler is the part of Xcode that converts your Swift source code into machine code. It also gathers all the other components that go into an app, which can include things such as images, icons, and sounds, and puts them into the application bundle. The application bundle contains everything the app needs to run.

This entire process is also known as building the app. If there are any errors come up during compilation (spelling mistakes in your code are a common cause of these), the build will fail. If compilation finishes without any errors, Xcode creates the application bundle, and then copies to its target — either the iPhone or the Simulator — and launches the app. All that happens with a single press of the Run button!

Changing the text

It’s a long-time computer programming tradition to write a program that simply displays “Hello, world!” when learning how to program in a new language or for a new platform. That’s why Apple made it part of the Single View Application template. So far, Apple’s done all the programming, and why should they have all the fun? Let’s take the app they’ve provided and use it to make your own.

Let’s update the text and make it a little less generic.

➤ In the Canvas, click on “Hello World”:

'Hello World', highlighted in both the Editor and the Canvas
'Hello World', highlighted in both the Editor and the Canvas

Notice that “Hello World” is highlighted in both the Canvas and the Editor. In the Canvas, the highlighting looks like a fine blue rectangle drawn around “Hello World”:

'Hello World', highlighted in the Canvas
'Hello World', highlighted in the Canvas

And in the Editor, the line Text("Hello World) is highlighted:

'Hello World', highlighted in the Editor
'Hello World', highlighted in the Editor

“Hello World” is highlighted in both because they’re different ways of looking at the same things. The Editor shows you the user interface in the form of code, while the Canvas shows you the user interface as it will appear to the user.

Let’s make changes to the text using both the Canvas and the Editor.

➤ In the Canvas, Command-click on “Hello World”. A pop-up menu appears, listing a number of actions you can take:

Hello World, after being Command-clicked
Hello World, after being Command-clicked

➤ Click the Inspect… item in the pop-up menu:

'Hello World' and the inspector
'Hello World' and the inspector

This brings up the Inspector, which displays the properties of “Hello World” and lets you change them. It may not be immediately apparent, but the Inspector is bigger than it appears.

If you scroll while the cursor is over the Inspector, you can see all the properties:

Scrolling the Inspector to the bottom
Scrolling the Inspector to the bottom

➤ Make sure the Inspector is scrolled to the top and enter “Hey there!” into the text field just below the Text heading:

Editing the text to say 'Hey there!'
Editing the text to say 'Hey there!'

➤ Click anywhere on the Inspector to dismiss it.

'Hey there', highlighted in the Editor and Canvas
'Hey there', highlighted in the Editor and Canvas

“Hello World” is now “Hey there!” The change you made in the Canvas is reflected in the Editor. The line that once read Text("Hello World") now reads Text("Hey there!").

Note: If the Canvas hasn’t updated itself to show the the new text, click the Resume button near the upper-right corner of the Canvas.

This ability to edit works both ways. Let’s try another change to the text, this time using the code.

➤ In the Editor, change Text("Hey there!") to the following:

Text("Welcome to my first app!")

The section of code near the line you just changed should now look like this:

struct ContentView : View {
  var body: some View {
    Text("Welcome to my first app!")
  }
}

The change you made in the Editor is reflected in the Canvas:

'Welcome to my first app!' in the Editor and Canvas
'Welcome to my first app!' in the Editor and Canvas

➤ Click the Run button.

You should see your changes when the app starts in the Simulator:

Simulator displaying Welcome to my first app!
Simulator displaying Welcome to my first app!

Making the text bolder

The text needs some sprucing up. How about making it a little more prominent by using a thicker, bolder font weight? Let’s try to do that, using both the Canvas and the Editor.

In the Canvas, Command-click on “Welcome to my first app!” and select Inspect… from the pop-up menu.

'Welcome to my first app!' and the Inspector
'Welcome to my first app!' and the Inspector

➤ In the Weight menu, select Semibold:

Choosing a new font weight for 'Welcome to my first app!
Choosing a new font weight for 'Welcome to my first app!

➤ Click anywhere on the Inspector to dismiss it.

'Welcome to my first app!' with semibold font weight, in the Editor and Canvas
'Welcome to my first app!' with semibold font weight, in the Editor and Canvas

➤ Click the Run button. “Welcome to my first app!” is now a little more pronounced:

'Welcome to my first app!' with semibold font weight, in the Simulator
'Welcome to my first app!' with semibold font weight, in the Simulator

Once again, the change you made in the Canvas has a matching change in the Editor. The line that defines the text now reads like this:

Text("Welcome to my first app!")
  .fontWeight(.semibold)

The newly added .fontWeight(.semibold) is a method that changes the Weight property of “Welcome to my first app!”. You’ll look at methods in a little more detail soon, but in the meantime, think of them as small pieces of code that belong to an object which make that object perform a specific task.

You use a method by calling it. This is done by first specifying the name of the object whose method you want to call, followed by a period (.), followed by the name of the method.

Hint: Any time you see the . character in Swift code, think of it as being shorthand for “Use this method on the object I just mentioned”. For example, you should read Text().fontWeight() as “Use the fontWeight method of my Text object.”

fontWeight is one of many methods that belong to the Text object. It changes the weight of its Text object’s font to the setting you provide it with. In this particular case, that setting is .semibold, which is a pre-defined value describing a font weight best described as “bold, but not too bold.”

Note: .semibold is the short version for the font weight setting. Its full name is Font.Weight.bold. Swift lets us get away with omitting the Font.Weight. part is because it knows that the only kind of settings you can provide the fontWeight() method are the Font.Weight kind.

Swift considers .fontWeight(.semibold) to be part of the same line as Text("Welcome to my first app!"). However, adding it as its own indented line makes it easier to read. The indentation is a convention that programmers use to say “this line is a continuation of the previous one,” and the Swift compiler simply ignores it.

Note: If you’re in an experimental mood, try editing the code for the text so that it’s all in a single line:

Text("Welcome to my first app!").fontWeight(.semibold)

and then press the Run button. The app still works, which means that Swift didn’t find anything wrong with the change. Once you’ve confirmed that the code works, change it back to its original formatting.

You’re probably asking “Why use formatting that the compiler’s going to ignore, anyway?” The answer is best summed up by notable MIT computer science professor Harold Abelson, who wrote “Programs must be written for people to read, and only incidentally for machines to execute.”

The compiler doesn’t care about the how the code is formatted, but a little formatting makes it easier for humans to read, understand, make changes to, and more easily find errors in code.

Now that you’ve made a change to the font weight of “Welcome to my first app!” in a graphical way, it’s time to try it in code.

➤ In the Editor, change the lines of code starting with Text so that the font weight is black instead of semibold:

Text("Welcome to my first app!")
  .fontWeight(.black)

If you made the change by entirely deleting .semibold, a pop-up appeared when you typed in the .:

Code completion appearing when changing the font weight
Code completion appearing when changing the font weight

This is Xcode being helpful with its code completion feature. It knows that what values you can put into .fontWeight modifier and provides them for you in a handy list. If Xcode presented this to you, choose Font.Weight black from the list.

Once you’ve made the change to the code, you’ll see a matching change in the Canvas. (If you don’t, click the Canvas’ Resume button.)

➤ Click the Run button. “Welcome to my first app!” is even more pronounced:

'Welcome to my first app!' with black font weight, in the Simulator
'Welcome to my first app!' with black font weight, in the Simulator

Changing the text’s color

Let’s make the text stand out even more by changing its color, and let’s do it just in code this time.

➤ In the Editor, change the lines of code starting with Text so that it looks like the following:

Text("Welcome to my first app!")
  .fontWeight(.black)
  .foregroundColor(.green)

While entering the code, as you type the . character that comes before color, Xcode will try to help you by presenting a code completion pop-up. It will present a list of features of the Text object that you might want to use:

Code completion appearing adding a new modifier to 'Welcome to my first app!'
Code completion appearing adding a new modifier to 'Welcome to my first app!'

You should either choose or type in the color() method, which sets the color of the Text object’s text to the color you specify. You specify that color between the parentheses — the ( and ) characters.

When you type the . inside these parentheses, Xcode presents a different list:

Code completion appearing adding specifying a color for 'Welcome to my first app!'
Code completion appearing adding specifying a color for 'Welcome to my first app!'

Choose or type in .green, and then check the Canvas to confirm that “Welcome to my first app!” is now green. Once again, you might need to click the Resume button near the upper right corner of the Canvas.

➤ Click the Run button. Just like the Canvas, “Welcome to my first app!” is now green in the app.

Object-oriented programming

Before you continue with the app, it’s time to look a little more closely at the topic of object-oriented programming. You may not realize it, but you’ve already been doing it!

Objects

Object-oriented programming is an approach that tries to manage the complexity of writing programs by dividing them into objects. Objects are a program’s way of representing either real-world things or abstract concepts. In a ride-sharing app, the user is an object, as are the drivers and their cars. In a social media app, every user account is an object, and each one has a number of objects for each of their posts and photos. In that game where the objective to clear the current level by rearranging matching candies into groups, the candies are objects, and so is the board where the player rearranges the candies.

In your app, you’ve already been working with a number of objects, both in the code and on the screen:

The objects
The objects

Let’s take a look at the code that defines one of these objects: the ContentView. We’ve looked at this once before, but this time we’ll go into more detail.

struct ContentView : View {

The whole line, translated from Swift to plain language, means “This is the definition of a struct named ContentView, and it’s a View.”

This translation is still a little technical, and could use some further explanation. The word struct is short for structure, which is a description of a kind of object in Swift. Swift has other kinds of objects that you’ll learn about later in this book, such as class.

A View is a kind of object that comes built-in with SwiftUI, and it represents anything that’s drawn onscreen, including the screen itself. Views can contain other views.

Since ContentView is a View, it has the same properties and can have the same methods that View has. Any properties and methods defined inside ContentView is in addition to any properties and methods it gets from being a View.

Hint: Any time you see the : character in Swift code, think of it as being shorthand for “is a”. For example, you should read ContentView : View as “ContentView is a view.”

Now that you have a rough idea on how to define objects, let’s move on to discussing how you can make them know things and do things: By discussing properties and methods.

Properties and methods

Objects are made up of at least one of these two kinds of things:

  1. Properties: Without data, computer programs have nothing to work with. Properties are where objects store their data. To continue with the ride-sharing app example, the user objects have properties such as the user’s name and current location. Each car object would have properties that store the make, model and year of the car, as well as its current location, and if applicable, the current passenger (which would be a user object). Properties are the things that objects know.
  2. Methods: These are groups of code that perform actions, often on the data in the object. In the example of the ride-sharing app, the user object might have a method to update the user’s current location. Each car object may have a method to update the car’s current location, or calculate its distance from a given user. That user, once again, would be a user object, and the calculation would require the current location data from that user object. Remember, to call a method, you first specify the name of the object whose method you want to call, followed by a period (.), followed by the name of the method. Methods are the things that objects do.

Let’s go back to reviewing the code. You’ll see that it already includes examples of both properties and methods!

Let’s start with properties. Right now, ContentView defines a property in addition to the properties it gets from being a View. That property is body, and it’s an object that acts as the container for all the objects on the screen that ContentView represents.

var body: some View {

This line, translated from Swift to plain language, means “This is the definition of a variable named body, and it’s a some View.” Basically, this is letting ContentView know what to display as its main body: The text field.

var is short for variable, which means two things:

  1. It’s a container for data.
  2. Its contents can change.

Unlike ContentView, whose definition says that it’s a View, the definition of body says that it’s a some View. The some in front of View broadens the possibilities for body. Without getting into too much detail for now, it means that body can contain either a View or something that behaves like a View (meaning that it’s an object isn’t a View, but can has the same properties and methods as View.

The contents of body are currently defined by these lines, which also include some examples of calling methods:

Text("Welcome to my first app!")
  .fontWeight(.black)
  .color(.green)

The first line creates a Text object, which is a piece of read-only text that gets drawn on the screen. It also sets the Text object’s content — which is one of its properties — to the text “Welcome to my app!”

The next two lines are examples of calling methods. Specifically, they call two methods on the Text object:

  1. The first line calls the Text object’s fontWeight() method, and provides it with a value representing the font weight black. In response to the message, fontWeight() applies the requested change in font weight to the original Text object, creating a new, bolder Text object.
  2. The second line calls the new, bolder Text object’s color() method, and provides it with a value representing the color green. In response to the message, color() applies the requested change in color to the new, bolder, Text object, creating an even newer green Text object with the same font weight.

This is the final Text object, which is drawn in the view.

This process is illustrated in the diagram below:

Method chaining explained
Method chaining explained

The process of applying two or more methods to an object, one after the other, is called method chaining. If you’ve done programming in JavaScript, you’ve probably seen this before.

You’ve already done some object-oriented programming up to this point:

  • You’ve created — or as programmers would say, instantiated — a Text object that says “Welcome to my app!”.
  • You called on the Text object’s methods to perform tasks: fontWeight() to make its text bold, and color() to change its color.

Phew — that was a lot to cover. Don’t worry if you don’t get everything right away or if you have trouble remembering everything; there’s a lot concepts here that may be brand new to you. We’ll review these concepts again and again through the book until they feel like second nature. Again: it’s all about learning via repetition.

Adding interactivity

Right now, the app simply displays text and then just sits there. That simply won’t do: It’s time to add some interactivity! You’ll do this by adding a button labeled “Hit me!”, which was one of the key items on the to-do list for the app.

You could add the button by adding code into the Editor, but you should also learn how to add user interface items the drag-and-drop way, using the Library and Canvas.

➤ Press the + button located near the upper right corner of the Xcode window. That’s the Library button:

The Library button
The Library button

The window for the Library will appear:

The Library window
The Library window

The Library is a collection of useful pre-made resources that you can drag and drop into your projects. These resources are divided into five major categories — Views, Modifiers, Snippets, Media, and Color — with one tab for each. It’s good for experimenting with ideas, and in a number cases, can save you some typing.

➤ With the Library window in view, make sure that the Views tab (the leftmost one, with the square-within-a-square icon) is selected. Remember, a view is anything that can be drawn on the screen, which means that a button is a view. Highlight the Button item in the Library list:

The Library window, with the Button view selected
The Library window, with the Button view selected

➤ Click and start dragging the Button item from the Library list and onto the Canvas. Drag it to just below the “Welcome to my first app!” text. A blue line should appear just below the text, and a pop-up window that reads Add Button to a new Vertical Stack along with existing Text. should appear at the bottom of the Canvas:

Dragging a button onto the view
Dragging a button onto the view

If you look at the Editor, you’ll notice some new code has appeared in the Editor. (Once again, you might need to click the Resume button near the upper-right corner of the Canvas.) Xcode should look like this:

The Editor and Canvas, with the button added
The Editor and Canvas, with the button added

Take a closer look at the code in the Editor:

struct ContentView : View {
  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(Font.Weight.black)
        .color(Color.green)
        Button(action: {}) {
            Text("Button")
        }
    }
  }
}

A couple of new elements have been added by dragging a button onto the Canvas. First, let’s take a look at the new Button object:

Button(action: {}) {
    Text("Button")
}

You may have noticed a couple of things about Button:

  • In its parentheses, there’s action:, followed by braces. You’ll put code that responds to the Button being pressed inside these parentheses.

Remember that in Swift, : means “is a”, and braces mark a group of lines of code, or mini-program. You might want to read action: {} as “action is a mini-program.”

  • Button contains a Text object, and that object determines what the button says.

The other new element is a VStack, which is short for vertical stack, which is a view that acts as a container for many views, and it arranges them in a vertical line. Before we look more closely at the VStack code, let’s format it so that it’s easier to see what it does.

➤ Change the indentation of the lines relating to the button so that the VStack code looks like the code below:

VStack {
  Text("Welcome to my first app!")
    .fontWeight(Font.Weight.black)
    .color(Color.green)
  Button(action: {}) {
    Text("Button")
  }
}

With the code formatted this way, it’s easy to see that the VStack contains two views: a Text and a Button. Since the Text appears first, it is positioned at the top of the VStack, with the Button just below it.

The diagram below shows the relationship between the VStack code and what you see in the Canvas:

VStack, in code and on the screen
VStack, in code and on the screen

According to the to-do list, the Button should say “Hit me!” and not “Button”, so let’s fix that.

➤ Change the line defining the Text inside Button so that it reads like this:

Button(action: {}) {
  Text("Hit me!")
}

➤ Click the Run button.

You should see the following in the Simulator:

The app, with the Hit me! button added
The app, with the Hit me! button added

➤ In the app, click the Hit me! button.

Nothing happens when you click the button. That’s to be expected, because you haven’t yet defined what should happen when the button is clicked.

Responding to button clicks

Let’s go back to the code that defines the button:

Button(action: {}) {
  Text("Hit me!")
}

As mentioned earlier, any code that should be executed when Button being is clicked should go inside the braces that follow action:. Let’s try some quick experimentation as a first step towards the goal of creating a pop-up when then user presses the button.

➤ Edit the code for the Button so that it looks like this:

Button(action: {
  print("Button pressed!")
}) {
  Text("Hit me!")
}

You’ll see what print() does in a moment.

Harnessing the power of print()

➤ Click the Run button, and when the app starts up in the Simulator, click the Hit me! button a couple of times.

You may initially be disappointed when clicking Hit me! seems to do nothing. But if you take a look at Xcode, you’ll see that a pane has opened at the bottom of its window, and every time you click Hit me! in the Simulator, a new “Button pressed!” appears in the pane:

The debug pane, displaying 'button pressed'
The debug pane, displaying 'button pressed'

print() is a function, which is a kind of method, except that it’s not attached to any object. Not being attached to an object means that you can use it without having to name an object first.

Hint: In Swift, any time you see a name that starts with a lowercase letter that’s immediately followed by parentheses, such fontWeight() or print() — you’re probably looking at the name of a function or method. The difference is that methods are preceded by the object they belong to (for example, Text("Hello").fontWeight(.black)), which functions don’t belong to obejcts and simply appear on their own (for example, print("Hi")).

The print() function takes some text and then prints it in Xcode’s Console, which is the pane that appeared when you pressed Hit me! in the Simulator. The Console is a read-only text area that displays messages from the compiler and other Xcode systems, as well as messages from your own apps via print().

print() is a debugging tool. This means that its purpose is to help the programmer figure out what’s going on in their program, and it’s often used to determine the cause of bugs (hence the name). You only see its results in Xcode, and only in apps that are run from Xcode in the Simulator or on devices that are connected to your computer. In apps that have been installed on a device and run on their own instead of Xcode, print() has no effect. It’s there only for the benefit of you, the programmer.

As you continue programming, you’ll find yourself using print() as an indicator to check that specific pieces of code are being executed, or to check on some value that your program has stored. In the code you just wrote, you’re using print() as a signal that control of the program had reached a specific point: the action: code for the Button.

The fact clicking Hit me! in the app causes print() to print “Button pressed!” in the Console is a good sign. It means that you can respond to a button being clicked. Congratulations — you’ve just written some interactive code!

You’re not done yet. Since print() is a debugging tool, users never see their results. From their point of view, clicking Hit me! still does nothing. You still have to make the button provide a response that the user can see. In order to do that, we need to go over the concept of state.

State and SwiftUI

A key part of programming SwiftUI is state. Rather than start with the computer science definition of state, let’s go with something that might be a little more familiar: the dashboard of a car.

The dashboard of a car
The dashboard of a car

The gauges and odometers are usually the most noticeable parts of a dashboard. They show the car’s current speed, fuel level, engine temperature and distance traveled, each of which is some kind of numerical quantity.

Dashboards also have warning lights, such as the “check engine” light, the low oil pressure warning light, the “it’s time to take the car to the shop for overpriced regular maintenance” light and the “someone’s not wearing their seat belt and will be very sorry if there’s an accident” light. Each of these lights is either on, indicating that there’s a problem that needs the driver’s attention, or off. This “on/off”, “yes/no” information can be described as binary.

The information on a car’s dashboard — speed, fuel level, whether on not someone in the car likes to live dangerously without a seat belt and so on — taken all together, is that car’s state.

The driver’s actions can change the car’s state, and the new state is immediately shown in the dashboard. For example, if the driver presses on the accelerator pedal, the car’s speed increases, which in turn causes the speedometer to display the car’s new speed. If the driver then presses on the brake pedal, the car slows down, and the speedometer automatically and immediately shows the new, slower speed.

Internal circumstances can also change the car’s state, and once again, this new state is shown in the dashboard instantly. As the car uses up fuel, the fuel gauge moves away from F and towards E. When the driver fills the tank, the fuel gauge immediately goes back to F.

Another example is the “maintenance” light. When a pre-determined amount of time passes or the car has traveled a pre-determined distance since it was last brought in for maintenance, the car’s state changes from not needing maintenance to needing it, and the “maintenance” light turns on. Once the car has been brought to the dealership or a mechanic for maintenance, the car’s state changes back to not needing maintenance and the light turns off.

There’s a term for every possible combination of every possible value of those things that make up the car’s state: the state space. With all the possible combinations of things that make up the car’s state — speed, fuel level, engine temperature, distance traveled, and so on — a car has a really large state space.

The one-button app’s state space

Unlike our car example, the one-button app you’re building has a much smaller state space. In case you’ve forgotten, let’s take a second look at what the app will look like by the end of this chapter:

What the app will look like by the end of this chapter
What the app will look like by the end of this chapter

It turns out that the app has only two states:

  1. Welcome: Simply displaying the “Welcome to my first app!” screen that you’ve already seen. This is what the user should see when they haven’t pressed the Hit me! button.
  2. Alert: Displaying the alert pop-up. This is what the user should see when they press the button.

The app’s state space is so small that it’s possible to draw it out in a state diagram:

State diagram for the one-button app
State diagram for the one-button app

The rectangles represent the app’s two states. The arrows are transitions, which are the ways to get from one state to another. Written beside each transition arrow is the reason for the change in state, formally known as a transition condition. The app has two transitions:

  1. From Welcome to Alert. This transition happens when the user presses Hit me!.
  2. From Alert to Welcome. This transition happens when the user dismisses the alert pop-up.

SwiftUI and state space

Coding a user interface in SwiftUI is similar to drawing a state diagram. Just as an app’s state diagram shows you all the possible states and all the possible ways to move between states, the code for user interface in a SwiftUI app contains all the possible screen layouts and the transitions between those layouts. It’s the state diagram for the user interface, in code form.

Let’s review the code for the user interface as it is right now:

struct ContentView : View {
  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .color(.green)
      Button(action: {
        print("Button pressed!")
      }) {
        Text("Hit me!")
      }
    }
  }
}

Remember, the body property of ContentView defines the layout of the screen. At the moment, body contains a VStack, which arranges a Text object and a Button object in a vertical stack, in that order. This covers the Welcome state. We’re missing the layout for the Alert state.

We’ll define that layout soon, but there’s something we need to take care of first: the app’s state.

Representing state in the app with a variable

Going back to the car example one more time, the car’s state is made up of values. Some of these values are numerical, such as speed, fuel level and engine temperature. Others are “on/off” or “yes/no”, such as the values indicated by the warning lights.

A program’s state is also made up of values, and these values are stored in variables. Variables allows the app to remember things. Think of them as temporary storage containers, each one storing a single piece of data. A variable — or more often, many variables — is the perfect place to store an application’s state.

You’ve already seen a variable in action: body, which contains everything that your app’s single screen displays — the “Welcome to my first app” text, and the Hit me! button. Now you’re going to create a variable to represent the app’s state.

The variable that represents the app’s state needs to store two possible values:

  1. A value representing the state where the app is not displaying the alert pop-up.
  2. A value representing the state where the app is displaying the alert pop-up.

This sounds like an “on/off”, “yes/no” value. In Swift and many other programming languages, such values are expressed as true and false and are called Boolean values, after English mathematician and logician George Boole.

Let’s create a variable that holds a Boolean value to keep track of whether or not the alert pop-up should be visible. We’ll give it the name alertIsVisible . When the app starts, the alert pop-up should not be displayed, which means that alertIsVisible’s initial value should be false.

➤ Add a line to the code for ContentView so that it looks like the following:

struct ContentView : View {
  @State var alertIsVisible: Bool = false
  
  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .color(.green)
      Button(action: {
        print("Button pressed!")
      }) {
        Text("Hit me!")
      }
    }
  }
}

Let’s look at the newly-added line, @State var alertIsVisible: Bool = false.

  • You’ve already seen the keyword var, which means “This is a variable named…” It’s followed by the name of the variable alertIsVisible.
  • The variable alertIsVisible is followed by :, which means “is a”, and Bool, which is Swift for “Boolean”. This means that alertIsVisible can contain one of two possible values, true or false.
  • Finally, the = false part means that alertIsVisible is initially set to the value false. Being a variable, you can expect this value to change at some point.

That unfamiliar new keyword, @State, needs a little more explaining. You’ve probably figured out that it marks alertIsVisible as a variable that stores information about the app’s state. It also tells Swift to watch for any changes to the contents of this variable and be ready to take action when that happens.

When the app starts, alertIsVisible’s value is false. It will stay that way forever unless we add code to change its value to true. We want that to happen when the user presses Hit me!.

➤ Change the code for ContentView so that it reads as follows:

struct ContentView : View {
  @State var alertIsVisible: Bool = false
  
  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .color(.green)
      Button(action: {
        print("Button pressed!")
        self.alertIsVisible = true
      }) {
        Text("Hit me!")
      }
    }
  }
}

You’ve just added a line that sets alertIsVisible to true, and you’ve done it inside Button’s block of code marked action:, which gets executed when the user presses the button.

You might be wondering about the keyword self, which got tacked on to the beginning of alertIsVisible. alertIsVisible is a feature of the ContentView object, and in code outside ContentView, you access it with the code ContentView.alertIsVisible. However, you’re inside ContentView, so you access it with the code self.alertIsVisible instead.

Defining the layout for the other state

It’s worth repeating: In SwiftUI, you define the layout for all possible states. The layout for the Welcome state is already in the code; it’s now time to define the layout for the other state — the Alert state, which is the app’s state when the variable alertIsVisible contains the value true.

The state of the app is now updated whenever the user presses the button. It’s time to write code to respond to that change in state.

The alert pop-up should appear when the user clicks the button. Fortunately, one of the methods that comes built into the Button object is called alert(), and it makes an alert pop-up appear if a given state variable’s value is true.

Rather than tell you how it works, it’s simpler (and more fun!) to show you:

➤ Change the code for ContentView so that it reads as follows:

struct ContentView : View {
  @State var alertIsVisible: Bool = false
  
  var body: some View {
    VStack {
      Text("Welcome to my first app!")
        .fontWeight(.black)
        .color(.green)
      Button(action: {
        print("Button pressed!")
        self.alertIsVisible = true
      }) {
        Text("Hit me!")
      }
      .alert(isPresented: self.$alertIsVisible) {
        Alert(title: Text("Hello there!"),
              message: Text("This is my first pop-up."),
              dismissButton: .default(Text("Awesome!")))
      }
    }
  }
}

➤ Press the Run button, and when the app starts up in the Simulator, press the Hit me! button.

You should see the following:

The alert pop-up
The alert pop-up

Let’s take a closer look at the code you just added:

.alert(isPresented: self.$alertIsVisible) {
  Alert(title: Text("Hello there!"),
        message: Text("This is my first pop-up."),
        dismissButton: .default(Text("Awesome!")))
}

alert() is a method on Button that presents an alert to the user. It requires two arguments, which is just computer science talk for “things that you provide to a method or function when you call it so that it can do its job.” Arguments can be either data or instructions (or in other words, code). In this case, you’re providing two pieces of data:

  1. A binding, or two-way connection to a Boolean state variable. In this case, that variable is alertIsVisible, and the two-way connection means that alertIsVisible’s value and the alert pop-up are tied together. Changing alertIsVisible to true causes the alert pop-up to appear, and the user dimissing the pop-up causes alertIsVisible’s value to be set to false. The $ in $alertIsVisible tells Swift that this is a two-way binding: changes to alertIsVisible affect the alert pop-up, and changes to the alert pop-up — affect alertIsVisble.
  2. An Alert object. This defines what the alert pop-up should contain.

The Alert object needs to be provided wth three things:

  1. title: This is the bold text at the top of the alert pop-up. You typically want this to be short — no more than a handful of words. The argument is a Text object, which you initialize with another argument: “Hello there!”
  2. message: This is the text below the title. You can make this a little longer than the title, but you shouldn’t make it longer than a sentence or two. As with the title, the argument is also a Text object that you initialize with “This is my first pop-up.” (Feel free to change any of these.)
  3. dismissButton: This is the button at the bottom of pop-up. It dismisses the pop-up when the user presses it. The argument a method that creates a button with the text “Awesome!”

Congratulations, you’ve just written your first iOS app! What you just did may have seemed like gibberish to you, but that shouldn’t matter. We’ll take it one small step at a time.

You can strike off the first two items from the to-do list already: Putting a button on the screen and showing an alert when the user taps the button.

Take a little break, let it all sink in and come back when you’re ready for more! You’re only just getting started…

Note: Just in case you get stuck, the complete Xcode projects have been included in the files that come with this book. They’re snapshots of the project as they would appear at the beginning and end of each chapter. That way, you can compare your version of the app to the book’s, or — if you really make a mess of things — continue from a known working version.

You can find the project files for each chapter in the corresponding folder.

Dealing with error messages

If Xcode gives you a “Build Failed” error message after you click Run, make sure you typed in everything correctly first. Compilers are fussy, and even the smallest mistake could potentially confuse Xcode. It can be quite overwhelming at first to make sense of the error messages that Xcode spits out. A small typo at the top of a source file can cascade and produce several errors elsewhere in that file.

One common mistake is differences in capitalization. The Swift programming language is case-sensitive, which means it sees Alert and alert as two different names. Xcode complains about this with a “<something> undeclared” or “Use of unresolved identifier” error. When Xcode says things like “Parse Issue” or “Expected <something>” then you probably forgot a brace } or parenthesis ) somewhere. Not matching up opening and closing brackets is a common error.

Tip: In Xcode, there are multiple ways to find matching braces to see if they line up. If you move the editing cursor past a closing brace, Xcode will highlight the corresponding opening brace, or vice versa. You could also hold down the key and move your mouse cursor over a line with a brace and Xcode will highlight the full block from the opening curly brace to the closing brace (or vice versa) — nifty!

You can see the block, here:

Xcode shows you the complete block for braces
Xcode shows you the complete block for braces

Tiny details are very important when you’re programming. Even one single misplaced character can prevent the Swift compiler from building your app.

Fortunately, such mistakes are easy to find:

Xcode makes sure you can’t miss errors
Xcode makes sure you can’t miss errors

When Xcode detects an error, it switches the pane on the left from the Project navigator, to the Issue navigator, which shows all the errors and warnings that Xcode has found. (You can go back to the project navigator using the small icons along the top.)

The screenshot above shows an intentional error: a missing comma between the title and message parameters of the Alert() view that should appear whenever alertIsVisible is true.

Click on the error message in the Issue navigator and Xcode takes you to the line in the source code with the error. It’s often not clear what went wrong when your build fails. For certain kinds of errors, Xcode lends a helping hand, suggests a fix and even offers to make the fix for you:

Fix-it suggests a solution to the problem
Fix-it suggests a solution to the problem

In this case, clicking the Fix button has Xcode follow its suggestion and insert the missing comma…

Fix-it implements its solution
Fix-it implements its solution

…and with the fix made, all the error indicators vanish. You have a working app again!

Errors and warnings

Xcode makes a distinction between errors (red) and warnings (yellow). Errors are fatal. If you get one, you cannot run the app until the error is fixed. Warnings are informative. Xcode just says, “You probably didn’t mean to do this, but go ahead anyway.”

In the previous screenshot showing all the error locations via arrows, you’ll notice that there is a warning (a yellow triangle) in the Issue navigator. We’ll discuss this particular warning and how to fix it later on.

Generally though, it is best to treat all warnings as if they were errors. Fix the warning before you continue and only run your app when there are zero errors and zero warnings. That doesn’t guarantee the app won’t have any bugs, but at least they won’t be silly ones!

The anatomy of your app

Let’s finish this chapter by looking at what goes on behind the scenes of your app.

You’ve already been introduced to the concept of objects, which are the building blocks of the app. The key objects in the app so far are:

  • ContentView: An object representing the app’s main screen.
  • body: A variable inside ContentView, which contains all the possible user interface elements on the main screen and defines all the possible ways how they can be laid out onscreen, for all circumstances. body contains the following objects:
    • A VStack view, which organizes other views into a vertical stack.
    • A Button view, which the user presses to initiate an action. It contains the following:
      • A Text view, which defines the text inside the button.
      • An action: method, which is called when the user presses the button.
      • An alert() method, which is bound to the alertIsVisible state variable. It gets called whenever the value contained in alertIsVisible changes.

You’ve also been introduced to the idea of state, which you can think of as a snapshot of the app’s current circumstances. Right now, the app has two possible states, which are represented by the contents of the alertIsVisible variable, which holds a Boolean value (that is, it contains either true or false):

  1. The Welcome state, where the user sees the main screen, with the “Welcome to my first app!” message. This is the app’s state when alertIsVisible is set to false.
  2. The Alert state, where the main screen is obscured by the alert pop-up. This is the app’s state when alertIsVisible is set to true.

In SwiftUI, apps work by having its objects and state affect and be affected by each other. An object can react to some external stimulus — which could come from the user, the operating system, another object, or a change in state — which then causes the object to change the app’s state.

Changing the state can affect objects, which perform some action or change some data in response, which in turn can affect the state.

As strange as it may sound, an app spends most of its time doing…absolutely nothing. It just sits there waiting for something — an event — to happen. We say that iOS apps are event-driven, which means that apps’ objects constantly listen for certain events to occur, and when they do, the objects respond as programmed.

When the user taps the screen, the app springs into action for a few milliseconds and then it goes back to sleep until the next event arrives.

Your part in this scheme is that you’re writing the source code that ties SwiftUI objects and state together, so that objects respond to changes in state, and make the appropriate changes to the state, which in turn causes the objects to respond, and so on.

The diagram below shows how the app that you’ve just completed works “under the hood”:

The anatomy of your app
The anatomy of your app

When the user presses the Hit me! button on the screen, its corresponding Button object executes the code in its action: method, which sets the contents of the alertIsVisible variable to true. alertIsVisible is a state variable, which means that it can affect objects in the app.

The Button object also calls its alert() method, which has a two-way binding to alertIsVisible. This means that this method is called every time alertIsVisible changes. The alert() method also defines an Alert view that should appear if alertIsVisible is set to true, which is the case when the button is pressed.

With the alert pop-up now displayed onscreen, the app returns back to what it does most of the time: waiting for the next user interaction. Since the alert is modal — that’s user interface jargon for “completely blocking everything else on the screen until the user deals with it” — the only possible user interaction within the app is to dismiss the alert by pressing its button.

When the user presses the Awesome! button on the alert to dismiss it, the two-way connection between the visibility of the alert and alertIsVisible causes alertIsVisible to be set to false. The user is now back on the main screen, and the app goes back to waiting for the next user interaction.

This app will spend more than 99% of its time waiting for input events — in this case, button presses — from the user. It will spend a very small amount of time, measured in milliseconds, on responding to those events.

WHile this app responds only to touch events from the user, the user can provide other events as well, such as shaking the device or speaking. The operating system can also provide events that notify apps when things happen, such as the user receiving an incoming phone call, or when the app has received incoming data from the internet, and so on. Everything apps do is triggered by events.

You can find the project files for the app up to this point under 02 - The One-Button App in the Source Code folder.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.