Chapters

Hide chapters

SwiftUI by Tutorials

Fifth Edition · iOS 16, macOS 13 · Swift 5.8 · Xcode 14.2

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

2. Getting Started
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.

SwiftUI is some of the most exciting news since Apple first announced Swift in 2014. It’s an enormous step towards Apple’s goal of getting everyone coding; it simplifies the basics so that you can spend more time on custom features that delight your users.

If you’re reading this book, you’re just as excited as I am about developing apps with this new framework. This chapter will get you comfortable with the basics of creating a SwiftUI app and live-previewing it in Xcode.

You’ll create a small color-matching game, inspired by our famous BullsEye app from our book UIKit Apprentice. The goal of the app is to try and match a randomly generated color by selecting colors from the RGB color space:

Playing the game
Playing the game

In this chapter, you will:

  • Learn how to use the Xcode canvas to create your UI side-by-side with its code, and see how they stay in sync. A change to one side always updates the other side.
  • Create a reusable view for the sliders seen in the image.
  • Learn about @State properties and use them to update your UI whenever a state value changes.
  • Present an alert to show the user’s score.

Time to get started!

Getting Started

Open the UIKit/RGBullsEye starter project from the chapter materials, and build and run:

UIKit RGBullsEye starter app
UIKit RGBullsEye starter app

This app displays a target color with randomly generated red, green and blue values. The user moves the sliders to make the other view’s color match the target color. You’re about to build a SwiftUI app that does the exact same thing, but more swiftly!

Exploring the SwiftUI Starter Project

Open the SwiftUI/RGBullsEye starter project from the chapter materials.

@main
struct RGBullsEyeApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}
struct ContentView: View {
  var body: some View {
    Text("Hello, world!")
      .padding()
  }
}

Previewing Your ContentView

In the ContentView file, below the ContentView structure, ContentView_Previews contains a view that contains an instance of ContentView:

struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}
Preview Resume button
Briries Faceza vewhum

Hello World preview
Morhu Yokjb hcibiod

Editor options
Ehijun okbeofg

Creating Your UI

Your SwiftUI app doesn’t have a storyboard or a view controller. The ContentView file takes over their jobs. You can use any combination of code and drag-from-object-library to create your UI, and you can perform storyboard-like actions directly in your code! Best of all, everything stays in sync all the time!

Some SwiftUI Vocabulary

Before you dive into creating your views, you need to know some vocabulary.

Creating the Target Color View

In RGBullsEye, the target color view, which is the color your user is trying to match, is a Color view above a Text view. But body is a computed property that returns a single View, so you’ll need to embed them in a container view. In this scenario, you’ll use a VStack (vertical stack).

Embed Text view in VStack
Ihvol Kuwp veed en LPdehx

Edit text in the Attributes inspector.
Edur burl od tse Uhphejiluf ilqsuwwov.

Insert Color into VStack
Azyokt Vuder uxru FCwibb

Color view in VStack
Guxum keuw ox PRkenm

Creating the Guess Color View

The guess color view looks a lot like the target color view, but with different text. It goes below the target color view, so you’ll just add it to the VStack.

VStack {
  Color(red: 0.5, green: 0.5, blue: 0.5)
  Text("R: ??? G: ??? B: ???")
    .padding()
  Color(red: 0.5, green: 0.5, blue: 0.5)
  Text("R: 204 G: 76 B: 178")
    .padding()
}

Creating the Button and Slider

The color sliders and Hit me! button go below the color blocks so again, you’ll just add them to your VStack.

Add Button to code
Ald Fuqdiz ju jega

Insert Slider in Vertical Stack
Ivdogv Mmopes op Jebhafaq Lhobs

Button & Slider in VStack
Gadxar & Wlezeg uy CHvons

Updating the UI

If the UI should update when a SwiftUI view property’s value changes, you designate it as a @State property. In SwiftUI, when a @State property’s value changes, the view invalidates its appearance and recomputes the body. To see this in action, you’ll ensure the properties that affect the guess color are @State properties.

Using @State Properties

Add these properties at the top of struct ContentView, above the body property:

@State var game = Game()
@State var guess: RGB
var target = RGB.random()
ContentView(guess: RGB(red: 0.8, green: 0.3, blue: 0.7))
ContentView(guess: RGB())

Updating the Color Views

Back in ContentView, edit the Color view above Text("R: ??? G: ??? B: ???") to use the target property of the game object:

Color(rgbStruct: game.target)
Random target color
Meccex zedpak becut

Color(rgbStruct: guess)
Guess color set in preview
Maobm cejib son ot fsanaad

Making Reusable Views

Because the sliders are basically identical, you’ll define one slider view, then reuse it for the other two sliders. This is exactly as Apple recommends.

Making the Red Slider

First, pretend you’re not thinking about reuse, and just create the red slider. You should tell your users its minimum and maximum values with a Text view at each end of the Slider. To achieve this horizontal layout, you’ll need an HStack.

Slider from 0 to 255
Zzosuq ycon 5 te 903

Add horizontal padding
Usw geyujiryiz longurf

Slider(value: $guess.red)
  .accentColor(.red)
Red slider value 0.3
Voj mroyaz lopea 9.1

Bindings

So back to that $. It’s actually pretty cool and ultra-powerful for such a little symbol. By itself, guess.red is just the value. It’s read-only. But $guess.red is a read-write binding. You need it here to update the guess color while the user is changing the slider’s value.

Text(
  "R: \(Int(guess.red * 255.0))"
    + "  G: \(Int(guess.green * 255.0))"
    + "  B: \(Int(guess.blue * 255.0))")
Text(guess.intString())
R value 76 = 255 * 0.3
Z rilui 39 = 496 * 1.9

Extracting Subviews

Next, the purpose of this section is to create a reusable view from the red slider HStack. To be reusable, the view needs some parameters. If you were to Copy-Paste-Edit this HStack to create the green slider, you’d change $guess.red to $guess.green and .red to .green. So these are your parameters.

Extract HStack to subview
Isszaxq QFgifn so jabceij

@Binding var value: Double
var trackColor: Color
Slider(value: $value)
  .accentColor(trackColor)
ColorSlider(value: $guess.red, trackColor: .red)
ColorSlider(value: $guess.green, trackColor: .green)
ColorSlider(value: $guess.blue, trackColor: .blue)
Three sliders
Tklao fhojann

ContentView(guess: RGB())

Live Preview

You don’t have to fire up Simulator to play the game: In the Preview toolbar, click the Live Preview button:

Live preview button
Reya mkiqaac qebyon

Playing the game
Mlepaqz ype jixi

Presenting an Alert

After using the sliders to get a good color match, your user taps the Hit Me! button, just like in the original UIKit game. And just like in the original, an Alert should appear, displaying the score.

Button("Hit Me!") {
  // action
}
@State var showScore = false
showScore = true
game.check(guess: guess)
.alert(isPresented: $showScore) {
  Alert(
    title: Text("Your Score"),
    message: Text(String(game.scoreRound)),
    dismissButton: .default(Text("OK")) {
      game.startNewRound()
      guess = RGB()
    })
}

Displaying the Target Values

There’s one last bit of functionality you need to implement. When showAlert is true, the target color label should display the correct color values, so your user can compare these with their slider values.

Text("R: ??? G: ??? B: ???")
Embed Text view in an if-else
Ulkik Pipl haal ej am oj-ehti

if !showScore {
  Text("R: ??? G: ??? B: ???")
    .padding()
} else {
  Text(game.target.intString())
    .padding()
}
Score!
Lkiro!

Making it Prettier

Your app has all its functionality, so now’s a good time to start improving how it looks. Instead of colored rectangles, how about circles?

Circle()
  .fill(Color(rgbStruct: game.target))
Circle()
  .fill(Color(rgbStruct: guess))
Color circles
Wuzov homvqob

Challenge

Challenge: Create a ColorCircle Subview

Create a ColorCircle subview so that you can replace the Circle().fill... lines with these:

ColorCircle(rgb: game.target)
ColorCircle(rgb: guess)

Key Points

  • The Xcode canvas lets you create your UI side-by-side with its code, and they stay in sync: A change to one side always updates the other side.
  • You can create your UI in code or the canvas or using any combination of the tools.
  • You organize your view objects with horizontal and vertical stacks, just like using stack views in storyboards.
  • Preview lets you see how your app looks and behaves with different initial data, and Live Preview lets you interact with your app without firing up Simulator.
  • You should aim to create reusable views. Xcode’s Extract Subview tool makes this easy.
  • SwiftUI updates your UI whenever a @State property’s value changes. You pass a reference to a subview as a Binding, allowing read-write access to the @State property.
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