SwiftUI — @State

Jullian Mercier
2 min readSep 8, 2019

--

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:

  • isFilledBool
  • $isFilledBinding<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.

--

--

Jullian Mercier
Jullian Mercier

Written by Jullian Mercier

Senior iOS engineer. jullianmercier.com. @jullian_mercier. Currently looking for new job opportunities.

No responses yet