Property Wrappers I
State, Binding and Observable.
Property Wrappers I
State, Binding and Observable.
0
0
Checkbox to mark video as read
Mark as read

In traditional imperative UI frameworks, such as UIKit, the developer manually updates the UI by calling specific methods whenever data changes. SwiftUI, however, leverages a declarative model where the framework itself monitors changes in the UI's data model and triggers re-renders automatically. This concept is known as automatic redraws.

It's important to understand that a re-rendering resets our view, even the init is executed again, so in order to keep the current value of our variables we can use the @State property wrapper.

This means that we can have regular constants since they won't change their value.

State property wrapper

@State will be the default property wrapper to use for the values that will change over time. For the values that won't change, we'll use constants:

struct ContentView: View {
	
	let title: String = "EducaSwift example"
    
    @State var count = 0
    
    var body: some View {
        VStack {
        	Text(title)
        		.font(.title)
            
            Text("Count: \(count)")
                .font(.headline)
                .foregroundStyle(.gray)
            
            Button("Increment") {
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
    }
}

We can use @State only for structs, like primitive types (String, Int, Bool...) or custom types. However, in case we want to use a class, it needs to be marked as @Observable.

Let's see an example where we create our own ViewModel which will store our constant and our count variable.

@Observable
class ViewModel {
    let title: String = "EducaSwift example"
    var count: Int = 0
}

Then we will be able to use our ViewModel as a @State variable, so any ViewModel property change will trigger a re-rendering in our view.

struct ContentView: View {
	    
    @State var viewModel = ViewModel()

    var body: some View {
        VStack {
            Text(viewModel.title)
            	.font(.title)

            Text("Count: \(viewModel.count)")
            	.font(.headline)
                .foregroundStyle(.gray)
                

            Button("Increment") {
                viewModel.count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
    }
}

Binding variable

Constants and @State variables can be passed as a parameters to a child view, then that child view can make changes to its own @State variable. Both variables will keep different values:

struct ContentView: View {

    let title: String
    @State var count: Int

    var body: some View {
        VStack {
            Text(title)
                .font(.title)

            Text("Parent Count: \(count)")
                .font(.headline)
                .foregroundStyle(.gray)

            Button("Increment Parent") {
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)

            ChildView(title: "Child Title", count: count)
                .padding(.top, 32)
        }
    }
}

struct ChildView: View {

    let title: String
    @State var count: Int

    var body: some View {
        VStack {
            Text(title)
                .font(.title)
            
            Text("Child Count: \(count)")
                .font(.headline)
                .foregroundStyle(.gray)
            
            Button("Increment Child") {
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
    }
}


However, we can bind the values of these two variables, meaning that if any of their values change, then the other one will update with the same value. To do this we just need to do 2 changes to our previous example:

  • Mark child view property as @Binding.
  • Use $ symbol on the parent.
struct ContentView: View {

    let title: String
    @State var count: Int

    var body: some View {
        VStack {
            Text(title)
                .font(.title)

            Text("Parent Count: \(count)")
                .font(.headline)
                .foregroundStyle(.gray)

            Button("Increment Parent") {
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)

            ChildView(title: "Child Title", count: $count) // <-- Use $ here
                .padding(.top, 32)
        }
    }
}

struct ChildView: View {

    let title: String
    @Binding var count: Int  // <-- Marked as @Binding

    var body: some View {
        VStack {
            Text(title)
                .font(.title)
            
            Text("Child Count: \(count)")
                .font(.headline)
                .foregroundStyle(.gray)
            
            Button("Increment Child") {
                count += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
    }
}


0 Comments

Join the community to comment

Be the first to comment

Accept Cookies

We use cookies to collect and analyze information on site performance and usage, in order to provide you with better service.

Check our Privacy Policy