SwiftUI — @State
Introduced with SwiftUI, the @State attribute is key to view rendering.
Because SwiftUI works closely with structs which are immutable types, whenever the body’s property is computed, the system must find a way to update its view with the latest data, hence the @State attribute.
struct ContentView {
@State var isFilled = false var body: some View {
Button(action: {
self.isFilled.toggle()
}) { () -> Image in
Image(systemName: isFilled ? “circle”: “circle.fill”)
}
}
}
When the @State property changes, the body is computed and the view re-rendered.
The State generic struct is « a persistent value of a given type, through which a view reads and monitors the value. »
A variable marked with @State doesn’t enable direct access to its underlying value — e.g the Bool value — instead it wraps around the value using the generic State struct which is a @propertyWrapper.
@propertyWrapper public struct State<Value> : DynamicProperty {
/// Initialize with the provided initial value.
public init(wrappedValue value: Value) /// Initialize with the provided initial value.
public init(initialValue value: Value) /// The current state value.
public var wrappedValue: Value { get nonmutating set } /// Produces the binding referencing this state value
public var projectedValue: Binding<Value> { get }
}
Declaring the @State `isFilled` variable gives access to three different types:
- isFilled — Bool
- $isFilled — Binding<Bool>
- _isFilled — State<Bool>
The State<Bool> type is the wrapper — doing all the extra work for us — that stores an underlying wrappedValue, directly accessible using isFilled property and a projectedValue, directly accessible using $isFilled property.
_isFilled.wrappedValue // Bool
_isFilled.projectedValue // Binding<Bool>
— Binding
A manager for a value that provides a way to mutate it.
Use a binding to create a two-way connection between a view and its underlying model.
struct ContentView: View {
@State private var recencyIndex = 0
let segmentedNames = ["First, Second, Third, Four"] var body: some View {
Picker(selection: $recencyIndex, label: Text("Picker")) {
ForEach(0..<segmentedNames.count, id: \.self) {
Text(self.segmentedNames[$0]).tag($0)
}
}.pickerStyle(SegmentedPickerStyle())
}
}
Looking closely at the Picker implementation, the selection argument takes a binding type.
Picker(selection: Binding<_>, label: _, content: () -> _)
When a segment is selected, an Int value will automatically be assigned to the recencyIndex @State variable using the two-way binding principle.
From a view’s body, a binding provides a convenient way to communicate with the @State variables. These are intended to be used locally so it is a good practice to mark them as private.
When selecting a segment in the Picker view, an Int value will automatically be assigned to the recencyIndex property.
Conversely, when assigning a value to recencyIndex, the matching segment will automatically be selected after the view is rendered.
Conclusion
The @State attribute is a means of communication with local variables which enables a view to be re-rendered using the latest provided data.
Follow me on Twitter for my latest articles.