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.