Building a SwiftUI app using MVVM architecture — Part 2: 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`.
For a better understanding on value types, checkout my article here.
@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 part 3 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.
For better understanding on the @State attribute, checkout my article here.
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.
Continue with Part 3: Communicating with the UI.