Your First iOS & SwiftUI App: An App from Scratch

Jan 11 2022 Swift 5.5, iOS 15, Xcode 13

Part 2: SwiftUI Data

12. SwiftUI State

Lesson Complete

Play Next Lesson
Next
Save for later
About this episode
See versions

See course reviews

See forum comments
Cinema mode Mark as Complete Download course materials
Previous episode: 11. Buttons & Actions Next episode: 13. Challenge: SwiftUI State

This is the iOS-15-only version of the alert code representing the state of the project at the end of this episode:

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

If you continue building up the message view until the end of the course, the result will look like this:

.alert("Hello there!", isPresented: $alertIsVisible) {
  Button("Awesome!") { }
} message: {
  let roundedValue = Int(sliderValue.rounded())
  Text("The slider's value is \(roundedValue).\n" + "You scored \(game.points(sliderValue: roundedValue)) points this round.")
}

And finally, if you continue into the next course, Your First iOS and SwiftUI App: Polishing the App, this is what the final version of the code would look like, in Episode 27—right before you learn to create a custom view instead of relying on the alert modifier.

.alert(
  "Hello there!",
  isPresented: $alertIsVisible,
  presenting: {
    let roundedValue = Int(sliderValue.rounded())
    return (
      roundedValue,
      game.points(sliderValue: roundedValue)
    )
  } () as (roundedValue: Int, points: Int)
) { data in
  Button("Awesome!") {
    game.startNewRound(points: data.points)
  }
} message: { data in
  Text("The slider's value is \(data.roundedValue).\n" + "You scored \(data.points) points this round.")
}

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 most noticeable parts of a car dashboard are probably its gagues and odometers. They show the car’s current speed, fuel level, distance traveled, and so on, each of which is some kind of numeric quantity.

Dashboards also have warning lights, such as the low oil warning light, or the the “it’s time to take the car to the shop for some overpriced maintenance” 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”, “true/false” information can be described as a boolean value.

So the information on a car’s dashboard — such as speed, fuel level, whether or not the car needs maintenance — taken all together, is a visualization of the car’s state.

Keep in mind that the dashboard isn’t the car’s actual state - it’s just a visualization of it.

To see what I mean, think about what happens when the driver changes the car’s state. For example, the driver presses the accelerator, and the car starts moving faster. The dashboard then updates to show the new speed. So the car’s state is how fast the car is actually moving - and the dashboard is just helping the driver visualize that fact.

Internal circumstances can also change the car’s state. For example, as you drive the car burns gas. The car’s state is how much gas is in the gas tank, and the dashboard hopefully updates the fuel indicator.

But what happens if the car’s state and dashboard aren’t in synch? For example, what if your dashboard breaks, and doesn’t accuratately show your car’s speed. Well that could be a big problem - you might get a ticket - or worse!

It turns out that this type of mistake is quite common while developing an app. That is, for your user interface might not accurately represent the internal state of your app. You may have sometimes come across an app with a bug like this - for example, an app says you have 5 new messages, but when you check you actually have a different amount.

One of the nice things about SwiftUI, is that you’re forced to develop your apps in such a way that your user interfaces and your state are always consistent, which prevents these frustrating types of bugs.

Let’s take another look at Bullseye, and think about what our app state would look like if we want to display a poupup alert when the user taps the Hit Me button.

It turns out that if when it comes to displaying a popup alert, there are only two possible conditions. Either the popup alert is on the screen, or it isn’t.

When the app first starts, the popup alert isn’t visible. That’s what we have so far.

What we want to happen is that when the user taps the Hit Me button, we want to display the pop-up message to the screen.

As you can see, at this point our app state for Bullseye is very simple: either the popup alert is visible, or it’s not. This is a boolean value, which means it’s either true, or false.

Do you remember how a few episodes ago, you learned that class and struct templates, and the instances of those templates, have data and functionality? Well, the boolean value to keep track of whether the popup alert is visible or not is an example of some data that we need to keep track of on our ContentView struct and instance.

Let’s see how we can do this.

Add this line:

@State private var alertIsVisible: Bool = false

Add this after the print statement:

// Remove the "Hello, SwiftUI"
self.alertIsVisible = true

Remember that in SwiftUI, you’re forced to develop your apps in such a way that your user interfaces and your state are always consistent.

To do this, you keep track of your app’s state using variables marked with @State. Each state variable will have an initial value - for example, we set alertIsVisible to false.

Then when the app starts up, iOS calls body to get a “dashboard” based on the current app state. For example, currently the body method returns the basic Bullseye user interface, but doesn’t show a popup. So far, so good.

But what happens when you set alertIsVisible to true? That is changing the app’s state, so it’s important that the user interface is updated to be consistent.

Well, since you already marked that variable with @State, iOS will automatically refresh the body. So it’s your job to make sure that body takes the app state into consideration, and displays an alert popup if alertIsVisible is true.

Let’s see how we can do this.

Sorry to interrupt, here—but!

If you’re targeting iOS 15, SwiftUI will no longer has a dedicated Alert type. Instead, you now create a View for an alert’s actions, and another for its message. This will look a little bit different than what Ray’s about to demonstrate. (Although, his code will still compile, with a deprecation warning.)

There are many ways to get the necessary data to an alert’s subviews. One of those is in the author notes, below. You can directly replace Ray’s alert code with that.

Add this to bottom of button

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

Congratulations, you’ve finally made your app interactive! 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 now strike off the next item from your programming to-do list: Show a popup when the user taps the Hit Me 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.