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.
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….
A window appears with your project checked.
➤ 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:
➤ Now type Welcome: You’ve changed the text in both the canvas and the code.
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:
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:
➤ 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.
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:
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:
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:
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:
➤ Select the Item Label placeholder and type Text(“Welcome”):
.tabItem { Text("Welcome") }
And there it is in the tab bar:
➤ 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:
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 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:
➤ Check out the Live Preview: Just swipe left or right and each page snaps into place.
➤ 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:
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:
➤ 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 tostruct
in Swift code. Like a class, it’s a complex data type that encapsulates properties and methods. If aView
has no uninitialized properties, you can create an instance of it with its default initializer. For example,WelcomeView()
creates an instance ofWelcomeView
.
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:
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.
➤ 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 withvar
.
Xcode now complains about ExerciseView()
in previews
, because it’s missing the index
parameter.
➤ Click the red error icon to display more information:
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()
.
➤ 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 ..<
:
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
:
➤ Not much information here. Click Open in Developer Documentation to see more detailed information.
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.
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 anHStack
. - 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.