Simran Preet Singh

I am a fullstack developer. If you give me one more task, my stack will overflow.
✍🏽 iOS | Python | SQL

Core Data Part 3 - NSManagedObjectContext | Simran Preet Singh

Core Data Part 3 - NSManagedObjectContext

April 14, 2024

Series

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()
  }
}

  1. Use the @FetchRequest property wrapper and create the property named folders. This will hold the list of all folders we create and save in Core Data.

  2. Now use the folders property in the List to show them individually.

  3. Here we are just showing the title of the FolderEntity in a Text. You will notice that title 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()
}

  1. We got the NSManagedObjectContext object from the environment that we injected in the CoreDataExampleApp.swift previously.

  2. In the action of the Save button, we first just return if the folderName is empty. We do not want to save folders with empty names. This is just a safety check, nothing related to Core Data.

  3. We create the folderEntity object. Here you will see that the FolderEntity initializer needs the NSManagedObjectContext object passed to it.

  4. We set the title of the folder on the folderEntity object

  5. This is the most important step. We have to explicitly call the save() method on managedObjectContext. 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 previously NSManagedObjectContext keeps track of the changes made to Data 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 this save() 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.