SwiftUI - Updating the UI

Updating the user interface in SwiftUI is rather simple with the newly introduced @State attribute which acts as a wrapper around a value.

struct ContentView: View {
    @State private var isSearching = false
    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                if self.isSearching {
                    Text("Building the UI")
                }
                List {
                    CategoryRow()
                }
            }            
         }.navigationBarItems(
              trailing: Button(action: {
                  self.isSearching.toggle() }
           ) {
              Image(systemName: "magnifyingglass")
                  .accentColor(
                      self.isSearching ? 
                      Color.gray: colorScheme == .light ?  
                      Color.black: Color.white)
            })
     }
}

When a button is tapped, the wrappedValue property will be accessed and mutated through the wrapper resulting in the struct’s body property being re-computed thus updating the UI.

Because SwiftUI uses structs which are value types, without the @State attribute we wouldn’t be able to toggle a variable without getting the following error message from the compiler Cannot use mutating member on immutable value: ‘self’ is immutable.

@State are designed to remain private as mentioned by Apple. Only access a state property from inside the view’s body (or from functions called by it). For this reason, you should declare your state properties as private, to prevent clients of your view from accessing it.

Because of their access control restriction, @State attributes work well whenever you need to update the UI which is not directly related to your data models. (e.g hiding/showing a view, updating a color..).

To work with your data models, Apple provide other means of communication which will be covered in the next part of this series.

Working with a button action is straightforward as the system provides a closure in which we can provide an action such as toggling a Bool value.

However if we look at some built-in components such as a Picker, we see the following initializer.

public init(selection: Binding<SelectionValue>, label: Label, @ViewBuilder content: () -> Content)

We need to provide some Binding type to handle the user tap gesture and update a variable accordingly. Apple has us covered with the @State variable which also provides a binding property out of the box.

Using either the projectedValue from the @State property wrapper or the following sign $ before the name of the variable will provide a Binding type.

struct PreferencesView: View {
    @State private var recencyIndex = 0
    private let segmentedValues = ["First" , "Second", "Third"]
    var body: some View {
        NavigationView {
            List {
                Section(header: Text("Recency")
                    .font(.system(size: 20))
                    .fontWeight(.semibold)) {
                        Picker(
                            selection: $recencyIndex, 
                            label: Text("Segmented Control")) {
                            ForEach(0..<segmentedValues.count, 
                                    id: \.self) {
                                Text(
                                   self.segmentedValues[$0]).tag($0
                                )
                            }
                         }.pickerStyle(SegmentedPickerStyle())
                 }
             }
         }
}

When the user taps on a specific segmented control, the recencyIndex local variable will be updated accordingly, same goes if a new value is assigned to the variable, providing a two-way binding style.