SwiftUI Property Wrappers in a Nutshell

October 8, 2019
swiftui development

SwiftUI is an interesting new technology from Apple that provides Swifty-DSL to allow a developer to describe a UI as the result of the function of its state, much like React.js and its predecessor, Elm.

The DSL includes a variety of “property wrappers” used to describe how the UI will bind to the state of the application. I think Apple’s API developers did the best they could with the names (see: Naming Is Hard) but I had still had difficulty understanding when I should use one over the other.

What I was concerned with was:

  • Are we binding to the item itself or the properties of the item?
  • Where does this item come from? Who provides it? How is it scoped?

Here is a quick-reference table to understand them:

Binds to Provided by
@State item local view
@Binding item parent
@ObservedObject properties parent
@EnvironmentObject properties global

@State

@State is the easiest to remember. If you have some transient state, that is for the local state of the UI that you are showing, but not a fundamental piece of the data in your underlying application, then you can make a quick @State variable in a pinch.

For example, let’s say you have a written a dialog which has a expandable section for “Advanced Settings” and whose visibility is controlled by a button. Whether or not that section is shown is not an essential part of your application’s data. What you need is a quick-and-dirty place to store whether or not that section is visible. This is what @State is good for.

It also appears that @State isn’t even necessarily stored in your struct. Instead, the same system that translates the View into a final UI rendering also keeps a lightweight storage facility where all the state variables go. When the View hierarchy is re-rendered, that storage area is read for the @State variables value.

Remember: @State is best for View- local data. Don’t put your core application code into this.


@Binding

@Binding is used to mark a variable’s binding that is imported into the view.

How you pass a binding from a parent to a child view is a little funky. Remember, you aren’t passing the variable in itself, but it’s binding. The @ObservedObject and @State and @EnvironmentObject property wrappers make two properties in your source code: The variable itself and an implicit binding to the variable. The companion binding property is the name of the property prefixed with $

Question: Can I use @Binding with stuff that is not a @State?

Yes, you can use it to bind Published properties of ObservedObjects

Question: Can a property of an parent’s ObservableObject be itself an ObservableObject?

Yes, it can and SwiftUI will observe both the property on the parent and the properties of the child


@ObservedObject

This is an object (conforming to ObservableObject) passed from the parent through the initializer of the struct. The view observes changes to it’s properties. It’s scoped to the current view unless you also pass it to a child view.

Question: How do we pass @ObservedObject from a parent view to child view?

You make a property on the child view that is declared as an @ObservedObject and when you specify the child in the hierarchy, you pass the parent @ObservedObject as a property of the child.

Question: How is that different from a @Binding?

A @Binding is for passing @Published properties and for @State, not for am entire @ObservedObject (unless of course that object is itself a @Published property)


@EnvironmentObject

This is mostly the same as @ObservedObject except that it’s scoped globally.

Question: When should you NOT use the @Environment binding? Why not ignore @ObservableObject entirely?

I haven’t experimented enough to see the performance differences, but think about it in terms of how you want to factor out your code and be wary of it in the same way you’d be wary of writing your program with global variables.


Update: Oct, 27, 2019

As it turns out, calling objectWillSend.send() on an ``@ObservableObjectdoesn't work unless that object has at least one var that uses the@Published` property wrapper. I’m not quite sure why that is yet.

comments powered by Disqus