Core Data Part 3 - NSManagedObjectContext
Series
- Core Data Part 1 - Data Model
- Core Data Part 2 - NSPersistentContainer
- Core Data Part 3 - NSManagedObjectContext 👈
In the last 2 parts we learnt about the Data Model
and Persistent Container
, in this part we will learn about the third remaning part of the Core Data stack which is NSManagedObjectContext
.
So as we learnt the NSPersistentContainer
actually persists data in the permamnent storage, NSManagedObjectContext
is a class that takes cares of objects of data models and the changes in them. Its the space provided by the NSPersistentContainer
to hold the objects while we wait to permanently store them someplace. So whenever you create a new DataModel
object, edit it, delete, before NSPersistentContainer
performs that operation permanently on that objects, its holds the information about the object and the operation to be performed on it in the NSManagedObjectContext
.
So lets create our NSManagedObjectContext
in the AppPersistenceContainer
we created previously. So after the line private var persistenceContainer: NSPersistentContainer
add this line of code in AppPersistenceContainer
public var managedObjectContext: NSManagedObjectContext {
persistenceContainer.viewContext
}
Your whole AppPersistenceContainer.swift
looks something like this:
import Foundation
import CoreData
class AppPersistenceContainer {
private var persistenceContainer: NSPersistentContainer
public var managedObjectContext: NSManagedObjectContext {
persistenceContainer.viewContext
}
init() {
persistenceContainer = NSPersistentContainer(name: "CoreDataExampleModel")
persistenceContainer.loadPersistentStores { description, error in
if let error {
print("❌ ERROR: Failed to load persistent store with error: \(error.localizedDescription)")
return
}
}
}
}
This is basically all we have to do create a NSManagedObjectContext
. But other than just creating it, we will teach you how to use as well. I assume you have basic understanding of SwiftUI and how to create Views in it and what are environment objects etc. If you do not please proceed with caution and learn those things first. Now lets just copy paste some of the code into your project. These are just SwiftUI views that you need to create a folder and show it on the UI.
The first one below is AddFolderSheet
, this is simply a sheet we will show to let user add the name of the FolderEntity
.
import SwiftUI
struct AddFolderSheet: View {
@Environment(\.dismiss) var dismiss
@State var folderName: String = ""
var body: some View {
NavigationStack {
Form {
TextField("Enter the name of your folder", text: $folderName)
}
.navigationTitle("Add folder name")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Save") {
defer { dismiss() }
}
}
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
}
}
}
#Preview {
AddFolderSheet()
}
Create another SwiftUI named FolderListView
which will hold the list of folders and open the AddFolderSheet
view.
import SwiftUI
struct FolderListView: View {
@State private var showAddFolderSheet = false
var body: some View {
List {
}
.navigationTitle("Folders")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Add a Folder", systemImage: "plus") {
showAddFolderSheet = true
}
}
}
.sheet(isPresented: $showAddFolderSheet, content: {
AddFolderSheet()
})
}
}
#Preview {
NavigationStack {
FolderListView()
}
}
The next step is to replace the contents in ContentView with the following
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationStack {
FolderListView()
}
}
}
#Preview {
ContentView()
}
Now run the app and hopefully it works like this
Now we will actually use the NSManagedObjectContext
and inject it into the app environment so it can be used any where. For that lets go back to CoreDataExampleApp.swift
and add this modifier to the WindowGroup
WindowGroup {
ContentView()
}
.environment(\.managedObjectContext, appPersistentContainer.managedObjectContext) // Add this modifier
After injecting it into the WindowGroup
we will modify both FolderListView
and AddFolderSheet
.
First we will use a property wrapper called @FetchRequest
in FolderListView
to fetch the folders from Core Data. It will look like magic now, but we will explain what it is and how it works in the next article. In this one just assume its some magical property wrapper that checks for any changes in the Core Data and puts the list items we ask it to in a property. So here we will ask it to fetch the list of folders. You wont see any change in the UI when you run the app because we have not added any folder yet. But we will make the neccessary changes in AddFolderSheet
later, we automatically see the newly created folders in FolderListView
.
import SwiftUI
struct FolderListView: View {
@State private var showAddFolderSheet = false
// 1.
@FetchRequest<FolderEntity>(sortDescriptors: []) var folders
var body: some View {
// 2.
List(folders) { folder in
// 3.
Text(folder.title ?? "")
}
.navigationTitle("Folders")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Add a Folder", systemImage: "plus") {
showAddFolderSheet = true
}
}
}
.sheet(isPresented: $showAddFolderSheet, content: {
AddFolderSheet()
})
}
}
#Preview {
NavigationStack {
FolderListView()
}
}
-
Use the
@FetchRequest
property wrapper and create the property namedfolders
. This will hold the list of all folders we create and save in Core Data. -
Now use the
folders
property in theList
to show them individually. -
Here we are just showing the
title
of theFolderEntity
in aText
. You will notice thattitle
is optional. Every property we create of a Core Data model is optional by defaul. We will create a clever solution to avoid these nil checks later in the series. Now for simplicity just put the default value to an empty string.
Now we will come to actually using NSManagedObjectContext
from the environment, create the FolderEntity
object and saving it in the Core Data persistence.
import SwiftUI
struct AddFolderSheet: View {
@Environment(\.dismiss) var dismiss
// 1.
@Environment(\.managedObjectContext) var managedObjectContext
@State var folderName: String = ""
var body: some View {
NavigationStack {
Form {
TextField("Enter the name of your folder", text: $folderName)
}
.navigationTitle("Add folder name")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Save") {
defer { dismiss() }
// 2.
if folderName.isEmpty { return }
// 3.
let folderEntity = FolderEntity(context: managedObjectContext)
// 4.
folderEntity.title = folderName
// 5.
try? managedObjectContext.save()
}
}
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
}
}
}
#Preview {
AddFolderSheet()
}
-
We got the
NSManagedObjectContext
object from the environment that we injected in theCoreDataExampleApp.swift
previously. -
In the action of the
Save
button, we first just return if thefolderName
is empty. We do not want to save folders with empty names. This is just a safety check, nothing related to Core Data. -
We create the
folderEntity
object. Here you will see that theFolderEntity
initializer needs theNSManagedObjectContext
object passed to it. -
We set the
title
of the folder on thefolderEntity
object -
This is the most important step. We have to explicitly call the
save()
method onmanagedObjectContext
. If we do not do this, we will see the folder created while we are running the app, but as soon as we quit the app and restart it, that folder created would not be persisted. As I told you previouslyNSManagedObjectContext
keeps track of the changes made toData Model
but it does not save it automatically. We have to tell it to save it explicitly. In the later parts of this series we will see on how we can create a system that we do not have to call thissave()
every time we make a change to a Core Data Data Model object.
Now lets run the app and create few folders and see them getting saved.
So in this article we learnt how to create a NSManagedObjectContext
and use it to create and save our objects of the data model and then fetch them from core data using @FetchRequest
property wrapper. In the next article we will learn more about @FetchRequest
and the actuall class behind it NSFetchRequest
and how we can use it in different ways to fetch data.