State Management in SwiftUI

Jun 20 2024 · Swift 5.9, iOS 17.2, Xcode 15.2

Lesson 01: Introduction to State Management with @State

Initializing @State Properties Demo

Episode complete

Play next episode

Next
Transcript

This demo explores proper initialization of @State properties in a SwiftUI view, using a simple counter app as an example. Time to begin!

Open the starter Counter Xcode project in the 05-Initializing-@State-Properties-Demo/Stater [TODO: FPE: Should the last word be “Starter” instead?] directory. This project contains a basic counter app that increments and decrements a count.

The app consists of a single file for demonstration purposes. In real-world scenarios, code is typically organized into multiple files. However, having everything in one file allows for easy observation of the entire app codebase.

In the Counter app, a CounterView contains a @State property called count. The goal is to understand when this count property initializes compared to when the CounterView initializes and when the CounterView’s body computes.

To observe the initialization of the count property, add a method that will be called when setting its initial value. Below the CounterView, add the following:

func initialCount() -> Int {
  return 0
}

Update the count property declaration to use this method:

@State private var count: Int = initialCount()

Add breakpoints to observe the sequence of events. Place breakpoints inside the initialCount method, inside the CounterView’s initializer, and inside the CounterView’s body. Add breakpoints by clicking the line number to the left of your code.

Build and run on the simulator. The first stop occurs inside the initialCount method, indicating that the @State property initializes before the view’s creation. This initialization only happens once, even though the CounterView can be re-initialized multiple times.

Continue execution to observe that the next stop is in the view’s initializer, followed by a stop in the view’s body. This sequence demonstrates that the @State property initializes before the view’s first initialization.

Before moving on, remove the initialCount method and initialize the count directly in its declaration:

@State private var count: Int = 0

Now, it’s time to explore a common pitfall: trying to initialize the view’s state inside the view’s initializer. First, deactivate the breakpoints by clicking each line number again.

Modify the CounterView’s initializer to take in an initial value for the count and set the count property to this value. Also, change the print statement in the initializer to reflect the count passed in:

init(count: Int) {
  print("Initializing CounterView with count: \(count)")
  self.count = count
}

Remove the initial value from the count property declaration. Now, the parent ContentView needs to provide the state to pass into the child CounterView. Add a parentCount @State property to ContentView, and initialize CounterView with this state:

struct ContentView: View {
  @State private var parentCount: Int = 0

  var body: some View {
    CounterView(count: parentCount)
  }
}

Move the buttons from the CounterView to the ContentView, and change them to update parentCount instead of count. You need this to be able to change the parentCount that’s passed into the CounterView:

struct ContentView: View {
  @State private var parentCount: Int = 0

  var body: some View {
    CounterView(count: parentCount)
    Button("Increment") {
      parentCount += 1
    }
    Button("Decrement") {
      parentCount -= 1
    }
  }
}

Build and run the app. Try to increment or decrement the count using the buttons. Notice that the count in the CounterView UI doesn’t change, even though the parentCount in ContentView does. Observe the print statements in the console to see how CounterView is being initialized with different values.

What’s happening? The @State value for CounterView was initialized once and only once with the value of 0. Despite new count values being passed in to CounterView, that @State isn’t re-initialized.

Put the app back into working order. Revert the changes made to the CounterView and ContentView to their original state, where the count initializes directly in its declaration, and the ContentView doesn’t pass any state to the CounterView. Ah, that’s better! The app should now function correctly again, with the count updating as expected when the buttons are pressed.

In conclusion, it’s important to understand the initialization sequence of @State properties in SwiftUI. Always initialize @State properties when declared to ensure views behave as expected.

In the next segment, you’ll explore how @State can be passed to subviews properly, further enhancing your ability to manage state in SwiftUI applications.

See forum comments
Cinema mode Download course materials from Github
Previous: Initializing @State Properties Next: Passing @State to Subviews