What is "@State" in SwiftUI?

·

4 min read

I am learning Swift and had no idea of "@State" at the first site.
Today, I explain what "@State" is and how it works that I have learned.

TL;DR

"@State" is a property wrapper of SwiftUI. The properties with it will be observed by SwiftUI, and the view will be updated whenever it changes its value.

The official doc says

A property wrapper type that can read and write a value managed by SwiftUI.

Use state as the single source of truth for a given value type that you store in a view hierarchy. Create a state value in anApp, Scene, orView byapplying the@Stateattribute to a property declaration and providing an initial value. Declare state as private to prevent setting it in a memberwise initializer, which can conflict with the storage management that SwiftUI provides:

struct PlayButton: View {
    @State private var isPlaying: Bool = false // Create the state.


    var body: some View {
        Button(isPlaying ? "Pause" : "Play") { // Read the state.
            isPlaying.toggle() // Write the state.
        }
    }
}

What is the property wrapper?

The property wrapper literally wraps the property. If a property is wrapped by a property wrapper, the property behaves as the property wrapper defines.
For example, if I define a property wrapper that validate email, the wrapped property by it will be validated. Here is the program for it:

@propertyWrapper
struct Email {
    var value: String?

    init(wrappedValue value: String?) {
        self.value = value
    }

    var wrappedValue: String? {
        get {
            return validate(email: value) ? value : nil
        }
        set {
            value = newValue
        }
    }

    private func validate(email: String?) -> Bool {
        guard let email = email else { return false }
        let regex = /^[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\\.)+[a-zA-Z]{2,}$/
        return email.firstMatch(of: regex) != nil
    }
}

struct User {
    @Email var mail: String?
}

var user = User(mail: "xxx")
print(user.mail) // nil because "xxx" is invalid

user.mail = "test@xxx"
print(user.mail) // nil because "test@xxx" is invalid

user.mail = "test@test.com"
print(user.mail) // Optional("test@test.com")

The custom property wrapper validate the string variable and return nil if that is invalid or return the value itself if that is valid.

As the example, we can define a custom property wrapper by declaring a struct with "@propertyWrapper" keyword.

Then, what is "@State"?

Okay, as we can learn what the property wrapper is, let's dive into what "@State" is.

Every application has many kinds of data like time, a word to filter a list, and a condition whether a user press a button, and so on.

To store such data, novices of Swift and SwiftUI like me may make a mistake like this:

struct ContentView: View {
    var number = 0
    var body: some View {
        VStack {
            Text("\(number)")
            Button("Increment") {
                number += 1
            }
        }
    }
}

However, the program triggers an error like this

Left side of mutating operator isn’t mutable: ‘self’ is immutable

The "number" will be immutable though it is defined as "var".
This is because that in the context of "View" in SwiftUI, modifying a property directly is not allowed. In case a struct is used in a "View", SwiftUI creates a new immutable instance of the struct whenever the view needs to be rendered, so directly modifying a property violates the immutability.

Instead, we have to use "@State" property wrapper to enable modification. With it, if the "number" is modified in some ways, the view will be updated with the new value.

In other words, SwiftUI observe the value of the "number" and if SwiftUI detect the modification of it, SwiftUI re-render the "View". The functionality is main feature of SwiftUI and called "data-binding".

The feature of "@State" and the rendering system of SwiftUI is similar to that of "useState" of React, I feel.

⭕️ Correct one

struct ContentView: View {
    @State var number = 0
    var body: some View {
        VStack {
            Text("\(number)")
            Button("Increment") {
                number += 1
            }
        }
    }
}

With the codes, the "number" will be updated by Button tap and the text for it will be rendered with the actual value of the "number"