Understanding Data Flow in SwiftUI

In this tutorial, you’ll learn how data flow in SwiftUI helps maintain a single source of truth for the data in your app. By Keegan Rush.

4.8 (32) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Observing Objects

To update the view when movieStore changes, you need to observe it. Adding the appropriate property wrapper helps you manage the UI update with the data change. Still in MovieList.swift, add the @ObservedObject property wrapper to movieStore, so it looks like this:

@ObservedObject var movieStore = MovieStore()

An observed object is a reference to a class that is observable. Using the @ObservedObject property wrapper notifies your view every time the observed object updates, so that the view updates for each change.

It works both ways, too. If you bind a text field to a property on an observed object, updating the text field will update the property on the observed object.

The reason that you can observemovieStore is that it is observable. This works because MovieStore conforms to the ObservableObject protocol.

Once again, build and run. Add a new movie. Hooray! The list updates automatically :]

New Movie Shows Instantly

Why @State Doesn’t Work Here

You may be wondering why you couldn’t use @State for movieStore.

@State is only for value types, so you can’t use it on movieStore which is a reference type. @ObservedObject allows you to respond to changes like @State does, but it has one difference.

Every time your data triggers an update in a view, SwiftUI recalculates the view’s body, thereby refreshing the view. When this happens, if you have an @ObservedObject declared in your view, SwiftUI recreates that object when your view refreshes.

State variables behave differently. When you use @State, SwiftUI takes control of the lifecycle of that property. It keeps the value of a @State property even when the view refreshes.

Fortunately, @State comes with a companion for reference types: @StateObject, which works exactly as @State, but specifically for reference types.

Once again, replace the declaration of movieStore with this:

@StateObject var movieStore = MovieStore()

Build and run. You’ll see the same behavior as before, but now, SwiftUI manages the lifecycle of movieStore.

A view of all the added movies

If you’re trying to decide whether to use @State, @StateObject or @ObservedObect, there are two questions you can ask about the property you’re declaring:

  • Is it a value type or a reference type? If you’re working with a value type, use @State.
  • Should SwiftUI manage the lifecycle of the property? If you’re only using the object in the view the property is declared in, @StateObject works fine. But, if the object is created or used outside the view, then @ObservedObject is a better match.

A Single Source of Truth

SwiftUI data flow is based on the concept that data has a single source of truth. With a single source of truth, there is one and only one place that determines the value of a piece of data.

For example, in UserView, you set the user’s name. There should only be one place in the code, one source of truth, that determines the value of the user’s name. Any other component that needs that username should refer to the source of truth. This keeps everything in sync so that when the source of truth changes, all its references change too.

Next, you’ll add a new view that illustrates this concept. Every movie has a genre, so wouldn’t it be convenient if the user could have a favorite genre? Then, when adding a new movie, this favorite genre will be the first suggestion.

Open AddMovie.swift. The AddMovie view has a picker that lets the user pick a genre for the movie. This picker would be perfect to reuse in UserView to set a favorite genre. But you won’t copy-paste the code from one view to another! Instead, you’ll create a reusable view.

Create a new view in Xcode by going to File ▸ New ▸ File… in the menu bar. Make sure you select SwiftUI View and click Next. Name it GenrePicker and click Create. Replace the contents of your new view with this:

import SwiftUI

struct GenrePicker: View {
  // 1
  @Binding var genre: String

  var body: some View {
    // 2
    Picker(selection: $genre, label: Spacer()) {
      // 3
      ForEach(Movie.possibleGenres, id: \.self) {
        Text($0)
      }
    }
    .pickerStyle(WheelPickerStyle())
  }
}

struct GenrePicker_Previews: PreviewProvider {
  static var previews: some View {
    // 4
    GenrePicker(genre: .constant("Action"))
  }
}

In the code above, you took the genre picker from AddMovie and put it into a reusable view. But one thing has changed: the addition of the @Binding property wrapper on genre.

Here’s what’s going on:

  1. The selected genre is stored in the genre property, annotated with the @Binding property wrapper.
  2. You create a picker wheel. When the user changes the selected row of the picker, it’ll set the genre, and vice-versa — setting genre changes the picker’s selected row.
  3. Rows in the picker are created by iterating over Movie.possibleGenres and displaying each value in a Text view.
  4. You need to pass in a value for genre in the preview, but passing a regular string won’t cut it. You need a Binding. Since it’s a preview, you can create a binding that does nothing with .constant.

A state property is a source of truth, while a binding is a reference to another property — usually a state property declared elsewhere. A binding lets you reference and update its source of truth.

The intention of GenrePicker is to let the user select a favorite genre so you can preset that genre in AddView or anywhere else you choose to use GenrePicker. This means GenrePicker doesn’t own the genre it’s setting, so you use @Binding.

Passing State as a Binding

Now that you have your GenrePicker ready, it’s time to put it to use.

Open AddMovie.swift again. Find the Section with the text title “Genre”. Replace its content with:

GenrePicker(genre: $genre)

This replaces the Picker with your new GenrePicker. Here in AddMovie, genre is a state property. By passing it into GenrePicker as $genre, you’re actually passing a binding to the property. Whenever the user modifies the genre inside GenrePicker, it’ll change the value of AddMovie‘s genre.

Build and run. Your picker should look like it did before, but now it’s reusable.

Add Movie screen with a reusable picker