Property Wrappers in SwiftUI – Understanding @State, @ObservableObject, @EnvironmentObject etc.

Share on facebook
Share on twitter
Share on pinterest

Property wrappers in SwiftUI provide us with variables with a specific logic depending on the type of the property wrapper. This logic is the core of the data flow concept in SwiftUI. To understand how they work and when to use which one, we explain every one step-by-step 👇

@State

State is probably the most frequently used property wrapper in SwiftUI.

You can easily declare a State by putting the @State keyword in front of a variable.

struct ContentView: View {

    @State var myVariable = 1

    var body: some View {
        //...
    }
}

Hint: State properties must always be related to a view in SwiftUI. So make sure you always declare them inside a View struct (but not inside the View’s body)!

So, what does a State property do? Well, you can read out and manipulate data just as you do with regular variables in Swift. But the key difference is that every time the data of a State changes, the related View gets rebuilt.

To see this in action, take a look at this example:

struct ContentView: View {
    
    @State var myInteger = 1
    
    var body: some View {
        VStack {
            Text("\(myInteger)")
            Button(action: {self.myInteger += 1}) {
                Text("Tap me!")
            }
        }
    }

We have an Integer variable wrapped into a State. In the related view, we have a Text object reading out the State’s data and a button. When we tap on the button, the State’s value get’s increased. As we learned, this manipulation causes the whole view to rerender and the Text to eventually displaying the updated value!

If you want to see another example how to use this functionality in practice, we recommend you to read our Login Page tutorial, where we talk about using States for implementing Text Fields.

@Binding

As we learned above, State properties must always relate to a specific View. But sometimes we want to have access to a State property from the outside, for example from child views.

For creating such a reference, we use the @Binding property wrapper. Suppose you want to create a Binding from a child view to the view containing the State property, just declare a variable inside the child view and mark it with the @Binding keyword.

struct ChildView: View {
    
    @Binding var myBinding: String
    
    var body: some View {
        //...
    }
}

You can then create the binding to the State by initialising the child view with referring to the State by using the “$” syntax.

struct ContentView: View {
    
    @State var myBinding = "Hello"
    
    var body: some View {
         ChildView(myBinding: $myBinding)
    }
    
}

You can then use the Binding as you do with States. For example when you update the Binding property, it also causes the State to change its data which then causes the view to rerender as well.

struct ContentView: View {
    
    @State var myInteger = 1
    
    var body: some View {
        VStack {
            Text("\(myInteger)")
            OutsourcedButtonView(myInteger: $myInteger)
        }
    }
    
}

struct OutsourcedButtonView: View {
    
    @Binding var myInteger: Int
    
    var body: some View {
        Button(action: {self.myInteger += 1}) {
            Text("Tap me!")
        }
    }
}

In the example above, the MotherView contains a State property holding an Integer as well as a Text for displaying that data. The ChildView contains a Binding to that State and a Button for increasing the Binding’s property value. When we tap the button, the State’s data get updated through the Binding which causes the MotherView to rerender and eventually displaying the new Integer.

Again, we recommend you to read our Login Page tutorial if you want to see how to implement Bindings in practice.

@ObservableObject and @ObjectObserver

ObservableObjects are similar to State properties which you already know. But instead of (re)rendering a view depending on its assigned data, ObservableObjects are capable of the following things:

● OberservableObjects are wrapping classes not variables

● These classes can contain data, for example, a string assigned to a variable

● We can bind one or multiple views to the ObservableObject (or better said, we can make these views observe the object).

● The observing views can access and manipulate the data inside the ObservableObject. When a change happens to the ObservableObject’s data all observing views get automatically rerendered, similar to when a State changes

Here’s an example on how to implement such an ObservableObject:

import SwiftUI
import Combine

struct ContentView: View {
    
    @ObservedObject var myObservedObject: MyObservableObject
    
    var body: some View {
        VStack {
            Text("\(myObservedObject.myInteger)")
            Button(action: {self.myObservedObject.increaseInteger()}) {
                Text("Tap me!")
            }
        }
    }
    
}

class MyObservableObject: ObservableObject {
    
    let objectWillChange = PassthroughSubject<MyObservableObject,Never>()

    var myInteger = 1 {
        didSet {
            objectWillChange.send(self)
        }
    }
    
    func increaseInteger() {
        myInteger += 1
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView(myObservedObject: MyObservableObject())
    }
}
#endif

Hint: Make sure you import the Combine Framework and conform to the @ObservableObject protocol. If you want to learn how to do this, check out this tutorial!

We can make a view observe such an ObserableObject by creating an @ObservedObject property. The view can then access the ObservableObject and also manipulate its data. In the example above, the View calls the ObservableObject’s increaseInteger function which increases the value of the myInteger variable inside the MyObservableObject. This manipulation causes the observing ContentView to rerender with eventually showing the updated value.

ObservableObjects are great for sharing data to multiple views, as you learn in the How to navigate in SwiftUI tutorial. Give it a try!

@EnvironmentObject

An EnvironmentObject is a property wrapper for a data model which, once initialised, can share data to all view’s of your app. The cool thing is, that an EnvironmentObject is created by supplying a ObservableObject.

Using EnvironmentObjects is suitable for passing data to several views at once without needing an “initialisation chain”. The exact explanation of what I mean by this can be found in this tutorial, where we use an EnvironmentObject to navigate through our SwiftUI app!

Conclusion 🎊

Today we talked about property wrappers in SwiftUI which are crucial for handling the data flow concept used within SwiftUI. Make sure you check out the linked tutorials!

If you want to see more, make sure you follow us on Instagram and subscribe to our newsletter to not miss any updates, tutorials and tips about SwiftUI and more!

Have any questions about this article? Write it in the comments below 👇

One reply on “Property Wrappers in SwiftUI – Understanding @State, @ObservableObject, @EnvironmentObject etc.”

This is a nice summary, but let me offer a couple minor comments.

Where you refer to @ObjectObserver, I think you mean @ObservedObject. Also, I don’t think you need to import Combine to use the ObservableObject protocol or the @ObservedObject wrapper.

Leave a Reply

Your email address will not be published. Required fields are marked *

small_c_popup.png

Covid-19 Forces you into quarantine?

Start Mastering swiftUI Today save your 33% discount

small_c_popup.png

Are you ready for a new era of iOS development?

Start learning swiftUI today - download our free e-book