Data Flow Through SwiftUI
Description: SwiftUI was built from the ground up to let you write beautiful and correct user interfaces free of inconsistencies. Learn how to connect your data as dependencies while keeping the UI fully predictable and error free. Familiarize yourself with SwiftUI’s powerful data flow tools and understand what the best tool is for each situation.
Principles of Data Flow
- Every time we read a piece of data in our view, we're creating a dependency for that view.
- Every time the data changes, the view has to change (read: re-render) to reflect the new value.
- Every piece of data that we're reading in the view hierarchy has a source of truth.
- The source of truth can live in the view hierarchy (if view-related, like a
collapsedstatus), or externally (our models).
- We should always have a single source of truth (no duplicate values).
- When a property has an associated property wrapper
@State, SwiftUI knows that it needs to persist the storage across multiple updates of the same view.
@Stateproperty as private, to really enforce the idea that state is owned and managed by that view specifically.
@Stateproperties are a special case of state variables, as SwiftUI knows when they change.
- And because SwiftUI knows that the
@Statevariables change the view
bodylook, SwiftUI knows that the view rendering depend on their state.
- When a
@Stateproperty changes, in the runtime SwiftUI will recompute the body of that view and all of its children.
- All the changes always flow down through your view hierarchy.
- We're able to do these re-rendering very efficiently because SwiftUI compares the views and renders again only what is changed.
- Views are a function of state, not of a sequence of events.
- By using the
Bindingproperty wrapper, we define an explicit dependency to a source of truth without owning it.
- We don't need to provide an initial value because the binding can be derived from a state.
- Primitives views such as
Sliderall expect a binding. We can read and write a
@bindingproperty (without owning it!)
Publishers (External changes)
- In case the event comes from an external source (a.k.a. not within the views), we use
- It's very important for
Views to receive events on the main thread.
ObservableObject (External Data)
- When the data is outside our View, swiftUI needs to know how to react on changes of that data
- By making our external data conform to
ObservableObjectprotocol, we are required to have a publisher, accessible via the synthesized
objectWillChangeproperty, that fires every time the data is about to change.
- All we need to do later is to associate an
@ObservedObjectproperty wrapper in the model and pass the
ObservedObjectto the view in its initialization.
- While SwiftUI
Views are value types (
Structs), any time we're using a reference type, we should be using the
@ObservedObjectproperty wrapper: this way SwiftUI will know when that data changes and can keep our view hierarchy up to date.
- Thanks to the
.environmentmodifier, we can add our
ObservableObjects into the environment.
- By using this
@EnvironemntObjectproperty wrapper, we can create a dependency to these environment objects.
Object Binding vs Environment
- We can build your whole app with
@Binding, but it can get kind of cumbersome to pass around the model from view to view.
EnvironmentObjectis a convenience for passing data around your hierarchy indirectly (without the need to pass the object to each view first).
@Stateis great for data that's view local, a value type, managed.
@Stateis allocated, created and managed by SwiftUI
@Stateis value type
@ObservedObjectis for representing external data to SwiftUI, such as a database.
@ObservedObjectis storage that we manage, which makes it great for the model that we already have.
@ObservedObjectis managed by us
@ObservedObjectis reference type