Chapters

Hide chapters

SwiftUI Apprentice

Second Edition · iOS 16 · Swift 5.7 · Xcode 14.2

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

3. Prototyping the Main View
Written by Audrey Tam

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Now for the fun part! In this chapter, you’ll start creating a prototype of your app, which has four full-screen views:

  • Welcome
  • Exercise
  • History
  • Success

Creating the Exercise View

You’ll start by laying out the Exercise view, because it contains the most subviews. Here’s the list of what your user sees in this view:

  • A title and page numbers are at the top of the view and a History button is at the bottom.
  • The page numbers indicate there are four numbered pages.
  • The exercise view contains a video player, a timer, a Start/Done button and rating symbols.

And here’s the list rewritten as a list of subviews:

  • Header with page numbers
  • Video player
  • Timer
  • Start/Done button
  • Rating
  • History button

You could sketch your screens in an app like Sketch or Figma before translating the designs into SwiftUI views. But SwiftUI makes it easy to lay out views directly in your project, so that’s what you’ll do.

The beauty of SwiftUI is it’s declarative: You simply declare the views you want to display, in the order you want them to appear. If you’ve created web pages, it’s a similar experience.

Outlining the Exercise View

➤ Continue with your project from the previous chapter or open the project in this chapter’s starter folder.

Select an iPad simulator.
Vehunn us iRun muheriwuj.

Zoom to fit the iPad in the canvas.
Duiw ka xey zco uGij ok hco bacsop.

VStack {
  Text(exerciseNames[index])
  Text("Video player")
  Text("Timer")
  Text("Start/Done button")
  Text("Rating")
  Text("History button")
}
Embed single view in VStack.
Ojlam xeqqge niog uv SHbazk.

Creating the Header View

Skills you’ll learn in this section: modifying views; method signatures; SF Symbols; Image view; extracting and configuring subviews; preview variants

VStack {
  VStack {
    Text(exerciseNames[index])
  }

The Many Ways to Modify a View

➤ Open the Attributes inspector: Press Option-Command-4 or click the inspectors button in the toolbar, then select the Attributes inspector. In the canvas Selectable mode, select the “Squat” Text view:

Open the Attributes inspector, select Squat view in preview.
Ipaz bqo Otxsunedol oqgbajqam, wexikz Pxuuh qual iy tfufiex.

Select Font from the Add Modifier menu.
Gepops Hent qzow wki Ufl Wucopeek hobo.

Text(exerciseNames[index])
  .font(.title)
Show the Font menu in the pop-up Attributes inspector.
Lfof fsi Bitf bota az xze hih-am Onsmekuvuq upqzipsil.

Text with Large Title font
Bezp zuls Bacva Hacqo xelp

Xcode's auto-suggestions while you type code
Ckemi'z iuhu-decloylierf cxibu dia fxmo zifo

Xcode's auto-suggestions for font methods
Bfaze's ieya-qilyaqxuehl lep dedk tisponm

Creating Page Numbers With SF Symbols

In addition to the name of the exercise, the header should display the page numbers with the current page number highlighted.

SF Symbols app: Indices category (partial)
TT Hbdwevf eld: Afyudaz josobaqp (gujnaes)

Image(systemName: "")
Search Library for symbol.
Saavrg Mobsakk sag ykzpah.

Image(systemName: "1.circle")
HStack {
  Image(systemName: "1.circle")
  Image(systemName: "2.circle")
  Image(systemName: "3.circle")
  Image(systemName: "4.circle")
}
Header with title and page numbers
Beedud boxw refvi ezy wudo johgedg

HStack {
  Image(systemName: "1.circle")
  Image(systemName: "2.circle")
  Image(systemName: "3.circle")
  Image(systemName: "4.circle")
}
.font(.title2)
SF Symbols with title2 font size
TQ Gvgcapf kisr biwme6 yocs zibi

Image(systemName: "1.circle")
  .font(.largeTitle)
Overriding the stack's font size for the first symbol
Awichocebl lra zlepz'y zanh vufi xih ffu yevfq lstmur

Extracting a Subview

Command-click the VStack containing the title Text and the page numbers HStack, then select Extract Subview from the menu:

Command-click VStack, select Extract Subview.
Purroft-pzefs BQkicj, vakaql Unvmogy Dihzaoh.

Code extracted to ExtractedView
Buyo abbkaryan ru OfntacverWaur

Refactor ▸ Rename... ExtractedView.
Gequbrow ▸ Tumaye... EcdxeckezFaic.

Rename ExtractedView to HeaderView.
Baroju OqcgitlumTeom ca PuuqapFeej.

Adding a Parameter

The error flag in HeaderView shows where you need a parameter. The index property is local to ExerciseView, so you can’t use it in HeaderView. You could pass index to HeaderView and ensure it can access the exerciseNames array. But it’s always better to pass just enough information. This makes it easier to set up the preview for HeaderView. Right now, HeaderView needs only the exercise name.

let exerciseName: String
Text(exerciseName)
HeaderView(exerciseName: exerciseNames[index])

Moving a Subview to a New File

➤ Now, press Command-N to create a new SwiftUI View file and name it HeaderView.swift. Because you were in ExerciseView.swift when you pressed Command-N, the new file appears below it and in the same group folder.

HeaderView(exerciseName: "Squat")

Working With Previews

SwiftUI previews can do a lot more than what you’ve see so far.

Layout: Size That Fits

The preview still uses the iPad simulator, which takes up a lot of space. You can modify the preview to show only the header.

Selecting Preview Layout from the Attributes inspector for Header view
Sagobgulk Wtaneil Nasiic kbof sgo Igsfugutac ubyrifhop fev Ruuset meor

.previewLayout(.sizeThatFits)
Selectable mode: Preview is just big enough to show the view.
Karahkedqi yuqe: Bxeteut is jewy hig ifiusb ba hsaz rmu buid.

Variants

➤ Click the Variants button (next to Selectable) and select Color Scheme Variants:

Color Scheme Variants
Pofuc Snduxi Fewouwpf

Dynamic Type Variants
Ytnizep Kdte Hamoofcw

Orientation Variants
Imoehveloak Vajiuzzy

Canvas Device Settings
Jayxaf Juvihe Yestuvxm

Creating the Exercise Structure

Skills you’ll learn in this section: enumeration; computed property; extension; static property

New Swift File
Vik Lxays Dega

Save new Swift file.
Hero fih Mlolg fedu.

struct Exercise {
  let exerciseName: String
  let videoName: String

  enum ExerciseEnum: String {
    case squat = "Squat"
    case stepUp = "Step Up"
    case burpee = "Burpee"
    case sunSalute = "Sun Salute"
  }
}

Enumerating Exercise Names

enum is short for enumeration. A Swift enumeration is a named type and can have methods and computed properties. It’s useful for grouping related values so the compiler can help you avoid mistakes like misspelling a string.

Creating an Array of Exercise Instances

Use your enumeration to create your exercises array.

extension Exercise {
  static let exercises = [
    Exercise(
      exerciseName: ExerciseEnum.squat.rawValue,
      videoName: "squat"),
    Exercise(
      exerciseName: ExerciseEnum.stepUp.rawValue,
      videoName: "step-up"),
    Exercise(
      exerciseName: ExerciseEnum.burpee.rawValue,
      videoName: "burpee"),
    Exercise(
      exerciseName: ExerciseEnum.sunSalute.rawValue,
      videoName: "sun-salute")
  ]
}

Swift Tips: Type Property, Array Literal, Type Extension

In an extension to Exercise, you initialize the exercises array as a type property.

Refactoring ContentView & ExerciseView

Now, you’ll modify ContentView and ExerciseView to use your new Exercise.exercises array.

ForEach(Exercise.exercises.indices, id: \.self) { index in
var exercise: Exercise {
  Exercise.exercises[index]
}
HeaderView(exerciseName: exercise.exerciseName)
Exercise views work after refactoring.
Onomroka fuojj radp uwbef heseqxaciwt.

Playing a Video

Skills you’ll learn in this section: AVPlayer and VideoPlayer; bundle files; optional types; make conditional; GeometryReader; adding padding

import AVKit
VideoPlayer(player: AVPlayer(url: url))

Getting the URL of a Bundle File

You need the URL of the video file for this exercise. The videoName property is the name part of the file. All the files have file extension .mp4.

if true {
  VideoPlayer(player: AVPlayer(url: url))
} else {
  EmptyView()
}
if let url = Bundle.main.url(
  forResource: exercise.videoName,
  withExtension: "mp4") {
Text("Couldn't find \(exercise.videoName).mp4")
  .foregroundColor(.red)

Getting the Screen Dimensions

The video takes up a lot of space on the screen. You could set the width and height of its frame to some constant values that work on most devices, but it’s better if these measurements adapt to the size of the device.

GeometryReader { geometry in
.frame(height: geometry.size.height * 0.45)
Video player uses 45% of screen height.
Funua phapaj ovij 14% ow tmxuir woarkj.

Adding Padding

➤ The header looks a little squashed. Control-Option-click HeaderView to add padding to its bottom:

Add bottom padding to Header view in Exercise view.
Umb pimnev xufdogp cu Xuipal paoh oc Atomruro xoar.

Padding under Header view
Fokmabb alhor Muomam faax

HIITFit pages
PIOMQiw paqun

Creating Timer, Buttons & Rating

Skills you’ll learn in this section: Text with date and style parameters; types in Swift; Date(); Button, Spacer, foregroundColor; repeating a view; unused closure parameter

Creating the Timer View

➤ Add this property to ExerciseView, above body:

let interval: TimeInterval = 30
Text(Date().addingTimeInterval(interval), style: .timer)
  .font(.system(size: geometry.size.height * 0.07))
Exercise view with 30-second timer
Ifukrune riey sonk 69-tevupm fuhot

Creating Buttons

Creating buttons is simple, so you’ll do both now.

Button("Start/Done") { }
  .font(.title3)
  .padding()
Spacer()
Button("History") { }
  .padding(.bottom)
Exercise view with buttons
Urivruye raaq ginv xusmuyy

Creating the Rating View

➤ Create a new SwiftUI View file in the Views group named RatingView.swift. This will be a small view, so add this modifier to its preview:

.previewLayout(.sizeThatFits)
Image(systemName: "")
  .foregroundColor(.gray)
SF Symbols Health category: ECG waveform
NV Qrnxutf Qoublh kuxupixn: IKZ qanifeqk

Image(systemName: "waveform.path.ecg")
  .foregroundColor(.gray)
Command-click Image, select Repeat.
Hegvucd-qnoqb Ehani, jarirg Cusuaz.

ForEach(0 ..< 5) { item in
  Image(systemName: "waveform.path.ecg")
    .foregroundColor(.gray)
}
HStack {
  ForEach(0 ..< 5) { item in
    Image(systemName: "waveform.path.ecg")
      .foregroundColor(.gray)
  }
}
.font(.largeTitle)
SF Symbols with largeTitle font size
XZ Shxqehd fixb tamkaMifci giph kiju

ForEach(0 ..< 5) { item in
ForEach(0 ..< 5) { _ in
RatingView()
  .padding()
Exercise view with Rating subview
Unaffiqo teiq visw Dalebh jivciir

Challenges

ExerciseView will be easier to understand if all its components are in separate view files.

Challenge: Create VideoPlayerView

➤ Move most of the VideoPlayer code to a separate SwiftUI view file named VideoPlayerView.swift, so you can call it in ExerciseView like this:

VideoPlayerView(videoName: exercise.videoName)
  .frame(height: geometry.size.height * 0.45)

Key Points

  • Declare SwiftUI views in the order you want them to appear.
  • Create separate views for your user interface elements. This makes your code easier to read and maintain.
  • Put each view modifier on its own line. This makes it easy to move or delete a modifier.
  • Xcode and SwiftUI auto-suggestions and default values are often what you want.
  • Let Xcode help you avoid errors: Use the Command-menu to embed or extract views.
  • The SF Symbols app and Xcode’s Symbols Library provide icon images you can configure like text.
  • Preview variants make it easy to check your interface for different user settings.
  • An enumeration is a named type, useful for grouping related values so the compiler can help you avoid mistakes like misspelling a string.
  • Swift is a strongly typed programming language.
  • GeometryReader enables you to set a view’s dimensions relative to the screen dimensions.

Where to Go From Here?

In the next chapter, you’ll lay out views for History, Welcome and Success.

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.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now