Chapters

Hide chapters

SwiftUI Apprentice

First Edition · iOS 14 · Swift 5.4 · Xcode 12.5

Section I: Your first app: HIITFit

Section 1: 12 chapters
Show chapters Hide chapters

Section II: Your second app: Cards

Section 2: 9 chapters
Show chapters Hide chapters

2. Planning a Paged App
Written by Audrey Tam

In Section 1 of this book, you’ll build an app to help you do high-intensity interval training. Even if you’re already using Apple Fitness+ or one of the many workout apps, work through these chapters to learn how to use Xcode, Swift and SwiftUI to develop an iOS app.

In this chapter, you’ll plan your app, then set up the paging interface. You’ll start using the SwiftUI Attributes inspector to add modifiers. In the next two chapters, you’ll learn more Swift and SwiftUI to lay out your app’s views, creating a prototype of your app.

Making lists: views and actions

This app has several screens. Here’s a sample to show you what the app will look like when you’re finished.

HIITFit screens
HIITFit screens

There’s a lot going on in these screens, especially the one with the exercise video. You might feel overwhelmed, wondering where to start. Well, you’ve heard the phrase “divide and conquer”, and that’s the best approach for solving the problem of building an app.

First, you need an inventory of what you’re going to divide. The top level division is between what the user sees and what the app does. Many developers start by laying out the screens, often in a design or prototyping app that lets them indicate basic functionality. For example, when the user taps this button, the app shows this screen. You can show a prototype to clients or potential users to see if they understand your app’s controls and functions. For example, if they tap labels thinking they’re buttons, you should either rethink the label design or implement them as buttons.

Listing what your user sees

To start, list the screens you need to create and describe their contents:

  • A Welcome screen with text, images and a button.
  • A title and page numbers are at the top of the Welcome screen and a History button is at the bottom. These are also on the screen with the exercise video. The page numbers indicate there are four numbered pages after this page. The waving hand symbol is highlighted.
  • The screen with the exercise video also has a timer, a Start/Done button and rating symbols. One of the page numbers is highlighted.
  • The History screen shows the user’s exercise history as a list and as a bar chart. It has a title but no page numbers and no History button.
  • The High Five! screen has an image, some large text and some small gray text. Like the History screen, it has no page numbers and no History button.

In this chapter and the next, you’ll lay out the basic elements of these screens. In Chapter 10, “Refining Your App”, you’ll fine-tune the appearance to look like the screenshots above.

Listing what your app does

Next, list the functionality of each screen, starting with the last two.

  • The History and High Five! screens are modal sheets that slide up over the Welcome or Exercise screen. Each has a button the user taps to dismiss it, either a circled “X” or a Continue button.
  • On the Welcome and Exercise screens, the matching page number is white text or outline on a black background. Tapping the History button displays the History screen.
  • The Welcome page Get Started button displays the next page.
  • On an Exercise page, the user can tap the play button to play the video of the exercise.
  • On an Exercise page, tapping the Start button starts a countdown timer, and the button label changes to Done. Ideally, the Done button is disabled until the timer reaches 0. Tapping Done adds this exercise to the user’s history for the current day.
  • On an Exercise page, tapping one of the five rating symbols changes the color of that symbol and all those preceding it.
  • Tapping Done on the last exercise shows the High Five! screen.
  • Nice to have: Tapping a page number goes to that page. Tapping Done on an Exercise page goes to the next Exercise page. Dismissing the High Five! screen returns to the Welcome page.

You’ll implement all of these in Chapter 6, “Adding Functionality to Your App”.

There’s also the overarching page-based structure of HIITFit. This is quite easy to implement in SwiftUI, so you’ll do it first, before you create any screens.

Creating Pages

Skills you’ll learn in this section: adding a Git repository to an existing project; visual editing of SwiftUI views; using the pop-up Attributes inspector; TabView styles

The main purpose of this section is to set up the page-based structure of HIITFit, but you’ll also learn a lot about using Xcode, Swift and SwiftUI. The short list of Skills at the start of each section helps you keep track of what’s where.

Adding source control to an existing project

In Chapter 1, “Checking Your Tools”, you created a new project. But HIITFit has a starter project with some assets and utility code. You’ll add files and code to this starter project.

➤ Open this chapter’s starter folder in Finder and locate HIITFit/HIITFit.xcodeproj. Double-click this file to open it in Xcode.

This project doesn’t have a Git repository. You could use the git init command line, but Xcode provides a quick way.

➤ In the Xcode menu, select Source Control ▸ New Git Repositories….

Xcode’s Source Control menu
Xcode’s Source Control menu

A window appears with your project checked.

Create a Git repository.
Create a Git repository.

➤ Click Create.

Now you’ll see markers when you modify or add files, and you can commit every major addition as you build this app. I’ll suggest the first few commits, then it will be up to you to make sure you commit a working copy before undertaking a new task.

Canvas and editor always in sync

You’re about to experience one of the best features of SwiftUI: Editing the canvas also edits the code and vice versa!

And here’s your first SwiftUI vocabulary term: Everything you can see on the device screen is a view, with larger views containing subviews.

Your next SwiftUI term is modifier: SwiftUI has an enormous number of methods you can use to modify the appearance or behavior of a view.

➤ First, in ContentView.swift, delete .padding() from the body closure: It’s a modifier that adds space around the Text view, and you don’t need it for now.

➤ In the canvas, click Resume if you don’t see ContentView, then double-click the Text view: You’ve selected “Hello world” in the code:

Double-click in canvas selects text in code editor.
Double-click in canvas selects text in code editor.

➤ Now type Welcome: You’ve changed the text in both the canvas and the code.

Editing code changes the view in the canvas.
Editing code changes the view in the canvas.

Note: Don’t press enter after typing Welcome. If necessary, refresh the preview.

The Text view just displays a string of characters. It’s useful for listing the views you plan to create, as a kind of outline. You’ll use multiple Text views now, to see how to implement paging behavior.

➤ Click anywhere outside the Text view to deselect “Welcome”, then select the Text view again. Press Command-D:

Command-D duplicates a view.
Command-D duplicates a view.

As you probably expected, you’ve duplicated the Text view. But look at the code:

VStack {
  Text("Welcome")
  Text("Welcome")
}

Your two Text views are now embedded in a VStack! When you have more than one view, you must specify how to arrange them on the canvas. Xcode knows this, so it provided the default arrangement, which displays the two Text views in a vertical stack.

➤ Change “V” to “H” to see the two views displayed in a horizontal stack:

HStack stacks its views horizontally.
HStack stacks its views horizontally.

➤ Type Command-Z to undo this change. SwiftUI’s defaults tend to match up well with what most people want to do.

➤ Change the second “Welcome” to Exercise 1. Then duplicate “Exercise 1” and change the third string to Exercise 2.

Three Text views in a VStack
Three Text views in a VStack

Now you have three distinct views to use in a TabView.

Using TabView

Here’s how easy it is to create a TabView:

➤ Change VStack to TabView:

A TabView has a tab bar.
A TabView has a tab bar.

Where did your Exercises go!? Well, they’re now the second and third tabs of a tab view, and there’s a tab bar at the bottom of the screen. It’s blank, because you haven’t labeled the tabs yet.

Here’s how you label the tabs. It’s actually very quick to do, but it looks like a lot because you’ll be learning how to use the SwiftUI Attributes inspector.

➤ In the canvas or in the editor, Control-Option-click the “Welcome” Text view to pop up its Attributes inspector:

Control-Option-Click to show the Attributes inspector.
Control-Option-Click to show the Attributes inspector.

Xcode Tip: The show-inspectors button (upper right toolbar) opens the right-hand panel. The Attributes inspector is the right-most tab in this panel. If you’re working on a small screen and just want to edit one attribute, Control-Option-click a view to use the pop-up inspector. It uses less space.

➤ Click in the Add Modifier field, then type tab and select Tab Item from the menu:

Select the Tab Item modifier.
Select the Tab Item modifier.

A new tabItem modifier appears in the editor, with a placeholder for the Item Label:

Text("Welcome")
  .tabItem { Item Label }

And a blue Label appears in the tab bar:

A tab item with default label
A tab item with default label

➤ Select the Item Label placeholder and type Text(“Welcome”):

.tabItem { Text("Welcome") }

And there it is in the tab bar:

Result of replacing the placeholder tab item label
Result of replacing the placeholder tab item label

➤ Replace the entire TabView with the following to add the labels for the other tabs:

TabView {
  Text("Welcome")
    .tabItem { Text("Welcome") }
  Text("Exercise 1")
    .tabItem { Text("Exercise 1") }
  Text("Exercise 2")
    .tabItem { Text("Exercise 2") }
}

Now you can see the three tab labels:

Three tab items with labels
Three tab items with labels

The canvas preview is a great way to get continuous feedback on the appearance of your view but, at this point, you probably want to see it in action. That’s where Live Preview can help.

➤ Click the Live Preview button to see how this would work on a device:

The Live Preview button
The Live Preview button

The first time takes a while, but subsequent live previews will refresh faster. If the Resume button appears, click it or press Option-Command-P.

➤ Now tap a tab label to switch to that tab. This is the way tab views normally operate. To make the tabs behave like pages, add this modifier to the TabView:

.tabViewStyle(PageTabViewStyle())

And now your tab labels are gone!

The page style uses small index dots, but they’re white on white, so you can’t see them.

➤ To make them show up, add this modifier below tabViewStyle:

.indexViewStyle(
  PageIndexViewStyle(backgroundDisplayMode: .always))

Now you can see the index dots:

TabView page style index dots
TabView page style index dots

➤ Check out the Live Preview: Just swipe left or right and each page snaps into place.

Live Preview: TabView page style in mid-swipe
Live Preview: TabView page style in mid-swipe

➤ You won’t be using tabItem labels for this app, so delete them. This is all the code inside the TabView closure:

TabView {
  Text("Welcome")
  Text("Exercise 1")
  Text("Exercise 2")
}

OK, you’ve set up the paging behavior, but you want the pages to be actual Welcome and Exercise views, not just text. To keep your code organized and easy to read, you’ll create each view in its own file and group all the view files in a folder.

Grouping files

Skills you’ll learn in this section: creating and grouping project files

You’re about to create Welcome and Exercise subviews by combining smaller subviews. SwiftUI encourages you to create reusable subviews for the same reason you create functions: Don’t Repeat Yourself. Even if you don’t reuse a subview, it makes your code much easier to read. And SwiftUI compiles subviews into efficient machine code, so you can create all the subviews you need and not worry about performance.

➤ Select ContentView.swift in the Project navigator. Create a new SwiftUI View file named WelcomeView.swift. Then, create another new SwiftUI View file named ExerciseView.swift.

Your Project navigator now contains three view files:

Project navigator after you add two SwiftUI view files
Project navigator after you add two SwiftUI view files

You’ll create several more view files, so now you’ll create a group folder with these three and name it Views.

➤ Select the three view files, then right-click and select New Group from Selection:

Create a group folder containing the three view files.
Create a group folder containing the three view files.

➤ Name the group Views.

Group folders just help you organize all the files in your project. In Chapter 5, “Organizing Your App’s Data”, you’ll create a folder for your app’s data models.

Passing parameters

Skills you’ll learn in this section: default initializers; arrays; let, var, Int; Fix button in error messages; placeholders in auto-completions

➤ Back in ContentView, replace the first two Text placeholders with your new views:

TabView {
  WelcomeView()   // was Text("Welcome")
  ExerciseView()  // was Text("Exercise 1")
  Text("Exercise 2")
}

Swift Tip: A View is a structure, shortened to struct in Swift code. Like a class, it’s a complex data type that encapsulates properties and methods. If a View has no uninitialized properties, you can create an instance of it with its default initializer. For example, WelcomeView() creates an instance of WelcomeView.

Now what? Your app will use ExerciseView to display the name and video for several different exercises, so you need a way to index this data and pass each index to ExerciseView.

Actually, first you need some sample exercise data. In the Videos folder, you’ll find four videos:

One of the exercise videos
One of the exercise videos

Note: If you prefer to use your own videos, drag them from Finder into the Project navigator. Be sure to check the Add to targets check box.

If you add your own videos, check Add to targets.
If you add your own videos, check Add to targets.

➤ In Chapter 5, “Organizing Your App’s Data”, you’ll create an Exercise data type but, for this prototype, in ExerciseView.swift, simply create two arrays at the top of ExerciseView, just above var body:

let videoNames = ["squat", "step-up", "burpee", "sun-salute"]
let exerciseNames = ["Squat", "Step Up", "Burpee", "Sun Salute"]

Swift Tip: An array is an ordered collection of structure instances or class objects. All the instances or objects in an array are the same type.

The video names match the names of the video files. The exercise names are visible to your users, so you use title capitalization and spaces.

➤ Still inside ExerciseView, above the var body property, add this property:

let index: Int

Swift Tip: Swift distinguishes between creating constants with let and creating variables with var.

Xcode now complains about ExerciseView() in previews, because it’s missing the index parameter.

➤ Click the red error icon to display more information:

Open the error to show the Fix button.
Open the error to show the Fix button.

Xcode often suggests one or more ways to fix an error. Many times, its suggestion is correct, and this is one of those times.

➤ Click Fix to let Xcode fill in the index parameter.

➤ Now there’s a placeholder for the index value — a grayed-out Int. Click it to turn it blue and type 0. So now you have this line of code:

ExerciseView(index: 0)

Swift Tip: Like other languages descended from the C programming language, Swift arrays start counting from 0, not 1.

Now use your index property to display the correct name for each exercise.

➤ Change "Hello, World!" to the exercise name for this index value.

Text(exerciseNames[index])

Back in ContentView.swift, Xcode is also complaining about the missing index parameter in ExerciseView().

Another error to fix
Another error to fix

➤ Fix this error the same way you did in ExerciseView.swift.

Now there’s a placeholder for the index value: What should you type there?

Looping

Skills you’ll learn in this section: ForEach, Range vs. ClosedRange; developer documentation; initializers with parameters

Well, you could pass the first array index:

ExerciseView(index: 0)

Then copy-paste and edit to specify the other three exercises, but there’s a better way. You’re probably itching to use a loop. Here’s how you scratch that itch. ;]

➤ Replace the second and third lines in TabView with this code:

ForEach(0 ..< 4) { index in
  ExerciseView(index: index)
}

ForEach loops over the range 0 to 4 but, because of that < symbol, not including 4. Each integer value 0, 1, 2 and 3 creates an ExerciseView with that index value. The local variable name index is up to you. You could write this code instead:

ForEach(0 ..< 4) { number in
  ExerciseView(index: number)
}

Developer Documentation

➤ This is a good opportunity to check out Xcode’s built in documentation. Hold down the Option key, then click ..<:

Option-click the ..< operator.
Option-click the ..< operator.

You’re viewing Xcode’s pop-up Quick Help for the half-open range operator. You can also view this information in the Quick Help inspector.

➤ Now Option-click ForEach:

Quick Help for ForEach
Quick Help for ForEach

➤ Not much information here. Click Open in Developer Documentation to see more detailed information.

Developer Documentation for ForEach: Three initializers
Developer Documentation for ForEach: Three initializers

The topic Creating a Collection of Views contains three initializers. The first is the one you’re using to loop over the array indices.

init(Range<Int>, content: (Int) -> Content)

➤ This ForEach initializer requires Range<Int>. Click this line to open the init(_:content:) page, then click Range in its Declaration to open the Range page. Sure enough, Range is “A half-open interval from a lower bound up to, but not including, an upper bound”, which matches the Quick Help you saw for ..<.

➤ Close the documentation window.

➤ You won’t need the TabView index dots. Open ContentView.swift and change:

.tabViewStyle(PageTabViewStyle())
.indexViewStyle(
  PageIndexViewStyle(backgroundDisplayMode: .always))

to:

.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))

Now, you’ll never show the index dots.

This is a good place to commit the changes you’ve made to your project with a commit message like “Set up paging tab view.”

Xcode Tip: To commit changes to your local Git repository, select Source Control ▸ Commit… or press Option-Command-C. If asked, check all the changed files. Enter a commit message, then click Commit.

You’re still in ContentView, so Live Preview your app. Swipe from one page to the next to see the different exercise names.

HIITFit pages
HIITFit pages

Key points

  • Plan your app by listing what the user will see and what the app will do.
  • Build your app with views and subviews, customized with modifiers.
  • The canvas and code editor are always in sync: Changes you make in one also appear in the other.
  • Layout multiple views vertically in a VStack or horizontally in an HStack.
  • The Attributes inspector helps you to modify a view or a preview.
  • ForEach lets you loop over a half-open range of numbers.
  • TabView can behave like a tab view or like a page controller.
  • You can preview or live preview your view in the canvas.

Where to go from here?

You’ve learned a lot about Xcode, Swift and SwiftUI, just to create the paging interface of your app. Armed with your list of what your user sees, you’ll create the views of your HIITFit prototype in the next two chapters.

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.
© 2024 Kodeco Inc.