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

Imagine jotting down something important in Notes, only to find your data is gone the next time you open the app! Fortunately, persistence is in excellent hands on iOS. All your notes, photos and other data are safe, thanks to Core Data. :]

There are a number of different technologies you can use when you need to store data across app launches. Core Data is the go-to solution on iOS. With impressive performance and a broad set of features, Apple’s Core Data framework manages the entire model layer of your app and handles persistence to your device’s storage disk.

In this Core Data with SwiftUI tutorial, you’ll refactor an app to add persistence and prevent the nightmare of losing your data when the app restarts. Along the way, you’ll learn to:

  • Set up Core Data in a project.
  • Use SwiftUI’s data flow to access what you need in the Core Data framework.
  • Define and create new model objects using Core Data.
  • Use Fetch Requests to retrieve objects from disk.

So buckle up and learn more about Core Data’s capabilities and how it works!

Getting Started

To start, download the project materials using the Download Materials button at the top or bottom of this tutorial. Then, open the starter project in Xcode. Then build and run.

Fave Flicks App Main Screen

Welcome to FaveFlicks, your own personal tally of your favorite movies. It’s a simple app that lets you add or delete a movie to the list. However, it has one glaring problem.

Yes, you guessed it right: The app does not persist data! This means that if you add some movies to your list, then restart the app, those movies you carefully added have gone. :[

Testing FaveFlick’s Persistence

To remove a movie from the list, swipe left and tap Delete.

Delete a row in the app

Next, tap the Plus button on the top-right to add one of your favorites.

Add a row in the app

You’ll see the Add Movie screen.

Add a new movie on the Add Movie screen

Each Movie object exists only in memory. They aren’t stored to disk, so closing the app removes your changes and reverts to my tasteful list of favorite movies.

Note: If you try to open the “add movie” screen a second time, then nothing happens. It’s a known Apple bug in SwiftUI. As a workaround, you need to update the UI in some way to add more movies. You can pull down the list to update the UI and then add more movies.

Force close the app to test its persistence. With the app in foreground, enter the fast app switcher. To do this, gently drag up from the bottom of the screen. If your device has one, double-tap the Home button to enable the fast app switcher.

Fast App Switcher on iPhone

Now, select FaveFlicks and swipe up to close the app. On the home screen, tap FaveFlicks to open it again.

Notice that your changes are gone, and the default movies are back.

Fave Flicks App Main Screen

It’s time to fix that. Begin by setting up Core Data.

Setting Up Core Data

Before you start setting up persistence, you should learn about the moving parts of Core Data, also known as the Core Data stack. The Core Data stack includes:

  • A managed object model which defines model objects, called entities, and their relationships with other entities. Think of it as your database schema. In FaveFlicks, you’ll define the Movie entity as part of the managed object model inside FaveFlicks.xcdatamodeld. You’ll use the NSManagedObjectModel class to access your managed object model in the code.
  • An NSPersistentStoreCoordinator, which manages the actual database.
  • An NSManagedObjectContext, which is an in-memory scratchpad that lets you create, edit, delete or retrieve entities. Usually, you’ll work with a managed object context when you’re interacting with Core Data.

With that out of the way, it’s time to get started!

Adding the Core Data stack

While it might seem daunting to set up the entire Core Data stack, it’s easy thanks to NSPersistentContainer. It can create everything for you. Open SceneDelegate.swift and add the following after import SwiftUI:

import CoreData

Core Data lives in its own framework, so you must import it in order to use it.

Now, add the following at the end of SceneDelegate:

// 1
lazy var persistentContainer: NSPersistentContainer = {
  // 2
  let container = NSPersistentContainer(name: "FaveFlicks")
  // 3
  container.loadPersistentStores { _, error in
    // 4
    if let error = error as NSError? {
      // You should add your own error handling code here.
      fatalError("Unresolved error \(error), \(error.userInfo)")
    }
  }
  return container
}()

Here’s what that does:

  1. Add a lazy property called persistentContainer to your SceneDelegate. The first time you reference the property, it will create an NSPersistentContainer.
  2. Create a container named FaveFlicks. You’ll see a file called FaveFlicks.xcdatamodeld if you look in the app’s list of files in the Project navigator. This file is where you will later design your Core Data model schema. The name of this file needs to match the name of the container.
  3. Instruct the container to load the persistent store, which simply sets up the Core Data stack.
  4. If an error occurs, it’s logged and the app is killed. In a real app, you should handle this by showing a dialog indicating the app is in a weird state and needs reinstalling. Any errors here should be rare and result from developer mistakes, so you’ll catch them before submitting your app to the App Store.

That’s it. That’s all you need to set up Core Data stack. Quite a journey, huh? :]

You’re going to also need a way of saving any data to disk, because Core Data does not handle that automatically. Still in SceneDelegate.swift, add the following method at the end of the class:

func saveContext() {
  // 1
  let context = persistentContainer.viewContext
  // 2
  if context.hasChanges {
    do {
      // 3
      try context.save()
    } catch {
      // 4
      // The context couldn't be saved.
      // You should add your own error handling here.
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
  }
}

This creates a method called saveContext() which does the following:

  1. Obtain the viewContext of the persistent container. This is a special managed object context which is designated for use only on the main thread. You’ll use this one to save any unsaved data.
  2. Save only if there are changes to save.
  3. Save the context. This call may throw an error, so it’s wrapped in a try/catch.
  4. In the event of an error, it’s logged and the app killed. Just like in the previous method, any errors here should only happen during development, but should be handled appropriately in your application just in case.

Now that you have the Core Data stack set up and a method to save changes, it’s time to wire this into the rest of the app.

Now, in scene(_:willConnectTo:options:), replace let contentView = MovieList() with the following:

let context = persistentContainer.viewContext
let contentView = MovieList().environment(\.managedObjectContext, context)

This simply grabs the same viewContext you used earlier and sets it as an environment variable on the MovieList SwiftUI view. The view will use this later to add and remove movies from the Core Data store.

Now add the following method to the end of SceneDelegate:

func sceneDidEnterBackground(_ scene: UIScene) {
  saveContext()
}

This instructs the app to call the save method you previously added when the app goes into the background. This is a good time to save data to disk. Later on, you’ll see how to save more frequently.

Build and run to check that the app still works. There are no functional changes just yet!