@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.
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.
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
}
count
property declaration to use this method:
@State private var count: Int = initialCount()
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.
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.
@State
property initializes before the view’s first initialization.
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.
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
}
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)
}
}
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
}
}
}
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.
@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.
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.
@State
properties in SwiftUI. Always initialize @State
properties when declared to ensure views behave as expected.
@State
can be passed to subviews properly, further enhancing your ability to manage state in SwiftUI applications.