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.