Core Data with SwiftUI Tutorial: Getting Started

In this Core Data with SwiftUI tutorial, you’ll learn to persist data in an app using @State, @Environment and @FetchRequest property wrappers. By Keegan Rush.

4.5 (57) · 1 Review

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

Creating the Data Model

Phew! Now that the Core Data stack is out of the way, it’s finally time to work on the main part of the app. In Xcode, open FaveFlicks.xcdatamodel. Right now it’s empty, but you’ll declare the Movie entity below. This is where you define the schema of your data model. You’ll add the relevant entities, which are the types of objects you can create, and define relationships to indicate how the entities are connected.

Click Add Entity.

Add an Entity to the Data Model

Xcode creates a new entity in the data model, named Entity by default. Double-click the name and change it to Movie.

Rename an Entity in the Data Model

Next, click the + icon under Attributes to add a new attribute. Name it title and set the type to String.

Add the Title Attribute

Finally, add two more attributes: One named genre of type String and another named releaseDate of type Date. Once you’re done, the Movie entity’s attributes will match the following:

Completed Movie Entity

Relationships and Fetched Properties

Although FaveFlicks only has a single Movie entity, you may run into relationships and fetched properties in apps with larger data models. A relationship is the same as a relationship in any database: It lets you define relationships between two entities.

However, Fetched properties is a more advanced Core Data topic. You can think of it as computed properties that work like weak one-way relationships. For instance, if FaveFlicks had a Cinema entity, it might have a currentlyShowingMovies fetched property that would fetch Movies that are currently in movie theaters.

Note: For more information on both — and much more! — check out Core Data by Tutorials.

Removing the Old Movie Struct

Open Movie.swift. At the beginning of this tutorial, Movie struct was the model object. Core Data creates its own Movie class so you need to remove Movie.swift. Delete Movie.swift by right-clicking it in the Project navigator and selecting Delete. In the resulting dialog, click Move to Trash.

Build the app. You’ll see a couple of errors that need fixing because you’ve just removed Movie.

Errors after Deleting Movie

Note: You’ll need to be precise and delete quite a bit of code for the old Movie struct in this section, so follow closely!

First, open MovieList.swift. You’ll find the movies for the list stored in a simple movies array. At the top of MovieList, change the line declaring the movies array to an empty array like below:

@State var movies: [Movie] = []

The @State property wrapper is a vital piece of the SwiftUI data flow. The class that declares this local property owns it. If anything changes the value of movies, the view that owns it will trigger an update of the UI.

Now, delete makeMovieDefaults(), since it’s no longer in use.

In addMovie(title:genre:releaseDate:), movies are created and added to the movies array. Remove its contents and leave it as a blank method. You’ll use it to create new instances of the Movie entity in a later section.

Finally, remove the contents of deleteMovie(at:). You’ll replace it later with code that deletes Core Data entities.

Using the New Movie Entity

Now that you created a Movie entity in the data model, Xcode will auto-generate it’s own Movie class that you’ll use instead. All entities in the data model are subclasses of NSManagedObject. It’s a managed object because Core Data handles its lifecycle and persistence for you, mainly through the use of the Managed Object Context.

The old Movie struct didn’t use optional properties. But, all NSManagedObject subclasses use optional properties for their attributes. This means you’ll need to make some changes in files that use Movie.

Note: There is an exception to all properties being optional: scalar types. For example, if your entity has a Float property and Use Scalar Type is enabled, then the generated property will be a non-optional of type Float.

Using an Entity’s Attributes in a View

Now, you’ll learn to use an entity’s attributes in a view. Open MovieRow.swift. Then, replace the body property with:

var body: some View {
  VStack(alignment: .leading) {
    // 1
    movie.title.map(Text.init)
      .font(.title)
    HStack {
      // 2
      movie.genre.map(Text.init)
        .font(.caption)
      Spacer()
      // 3
      movie.releaseDate.map { Text(Self.releaseFormatter.string(from: $0)) }
        .font(.caption)
    }
  }
}

The structure of the view is exactly the same, but you’ll notice that all the movie attributes are mapped to Views.

All attributes on a Core Data entity are optional. That is to say, the title attribute is type String?, referenceDate is type Date? and so on. So, now you’ll need a method to get the optional’s value.

Inside a ViewBuilder, like MovieRows body property, you can’t add control flow statements such as if let. Each line should be either a View or nil.

The line marked with 1, 2 and 3 above are a Text view if the attributes are non-nil. Otherwise, it’s nil. It’s a handy way to deal with optionals in SwiftUI code.

Finally, build and run. You removed the old Movie struct and replaced it with a Core Data entity. As your reward, you now have an empty view rather than a list of classy films. :]

Empty Movie List

If you create a movie, nothing happens. You’ll fix this next.

Using Environment to Access Managed Object Context

Next, you’ll learn to access objects from a managed object context. Back in MovieList.swift, add the following line under the declaration of movies:

@Environment(\.managedObjectContext) var managedObjectContext

Remember earlier you set the managedObjectContext environment variable on MovieList? Well, now you’re declaring that it exists and, therefore, can access it.

@Environment is another important piece of SwiftUI data flow that lets you access global properties. When you want to pass an environment object to a view, you pass it in when creating an object.

Now add the following method to MovieList.swift:

func saveContext() {
  do {
    try managedObjectContext.save()
  } catch {
    print("Error saving managed object context: \(error)")
  }
}

When you create, update or delete entities, you do so in your managed object context — the in-memory scratchpad. To actually write the changes to disk, you must save the context. This method saves new or updated objects to the persistent store.

Next, find addMovie(title:genre:releaseDate:). The method is still blank from when you removed the old Movie, so replace it with the method below to create new Movie entities:

func addMovie(title: String, genre: String, releaseDate: Date) {
  // 1
  let newMovie = Movie(context: managedObjectContext)

  // 2
  newMovie.title = title
  newMovie.genre = genre
  newMovie.releaseDate = releaseDate

  // 3
  saveContext()
}

Here, you:

  1. Create a new Movie in your managed object context.
  2. Set all the properties of the Movie that are passed as parameters into addMovie(title:genre:releaseDate:).
  3. Save the managed object context.

Build and run and create a new movie. You’ll notice a blank list.

Empty Movie List

That’s because you’re creating Movies, but you’re not retrieving them to display in the list. In the next section, you’ll fix this, and you’ll finally see movies in the app again.