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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Core Data with SwiftUI Tutorial: Getting Started
20 mins
- Getting Started
- Testing FaveFlick’s Persistence
- Setting Up Core Data
- Adding the Core Data stack
- Creating the Data Model
- Relationships and Fetched Properties
- Removing the Old Movie Struct
- Using the New Movie Entity
- Using an Entity’s Attributes in a View
- Using Environment to Access Managed Object Context
- Fetching Objects
- Predicates
- Testing the Results
- Deleting Objects
- Where to Go From Here?
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.
Xcode creates a new entity in the data model, named Entity by default. Double-click the name and change it to Movie.
Next, click the + icon under Attributes to add a new attribute. Name it title and set the type to String.
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:
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.
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
.
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
.
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 View
s.
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 MovieRow
s 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. :]
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:
- Create a new
Movie
in your managed object context. - Set all the properties of the
Movie
that are passed as parameters intoaddMovie(title:genre:releaseDate:)
. - Save the managed object context.
Build and run and create a new movie. You’ll notice a blank 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.