SwiftUI & Core Data

Mourad Kirat
7 min readFeb 18, 2024

--

Core Data:

Core Data is a framework provided by Apple for managing the model layer objects in an application. It offers an object graph and persistence framework, enabling data organized by the relational entity-attribute model to be serialized into XML, binary, or SQLite stores.

Widely utilized in iOS and macOS applications for data storage and management, Core Data is based on an object-oriented data model and seamlessly integrates with the Xcode development environment.

The framework comprises essential classes such as NSManagedObject, NSManagedObjectContext, NSManagedObjectModel, NSFetchRequest, and NSPersistentStoreCoordinator, which facilitate managing the data model and its interactions.

Core Data streamlines the process of managing the application’s data model and provides utilities for data migration and manipulation. It stands as a powerful and extensively adopted technology for data persistence within Apple’s ecosystem.

Follow step-by-step instructions for working with Core Data:

Step 1: Create your project in Xcode with SwiftUI.

Step 2: Create a Core Data file:

Then

Finally, enter the name of the entity followed by ‘.xcdatamodeld’, like this: ‘YourEntityName.xcdatamodeld’.

Step 3: Create Entity and Attributes

In my project, I use ‘Person’ as the entity with attributes: ‘id’, ‘name’, ‘age’, and ‘address’:

Step 4: Set the codegen to ‘Manual/None’.

Step 5: Create NSManagedObject

Step 6: In your Core Data properties, use the same name as the Core Data file for ‘entityName: ’.

Step 7: Create your data manager and add the Core Data container with the model name

Import Statements :

The code begins with import statements for the necessary frameworks:

import Foundation
import CoreData

DataManager Class :

The code defines a class called “DataManager” that inherits from NSObject and conforms to the ObservableObject protocol:

class DataManager: NSObject, ObservableObject {

Core Data Container :

Inside the DataManager class, there is a property called “container” of type NSPersistentContainer.

This container is responsible for managing the Core Data stack and provides access to the managed object context:

let container: NSPersistentContainer = NSPersistentContainer(name: "Person")

The container is initialized with the name “Person”, which is the name of the Core Data model file.

Initialization :

The code overrides the default initializer for the DataManager class. Inside the initializer, the Core Data container is loaded with persistent stores. If there is an error during the loading process, a fatal error is triggered:

override init() {
super.init()
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
}
}

Complete code of the data manager:

//
// DataManager.swift
// CoreDataExampleApp
//
// Created by Mourad KIRAT on 18/02/2024.
//

import Foundation
import CoreData
class DataManager: NSObject, ObservableObject {

// Add the Core Data container with the model name
let container: NSPersistentContainer = NSPersistentContainer(name: "Person")

// Default init method. Load the Core Data container
override init() {
super.init()
container.loadPersistentStores { _, error in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
}
}
}

Step 8: In your view, call ‘viewContext’ and your attributes

import SwiftUI

import CoreData

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [])
private var people: FetchedResults<Person>
// this exeample for my atributes
@State private var name = ""
@State private var age = ""
@State private var address = ""

Step 9 : Functions for creating and deleting

// Here is an example with the Entity 'Person'

private func save() {

let newPerson = Person(context: viewContext)
newPerson.name = name
newPerson.age = Int64(age) ?? 0
newPerson.address = address

do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}

// Reset fields after saving
name = ""
age = ""
address = ""
}

// delete Person
private func delete(offsets: IndexSet) {

offsets.map { people[$0] }.forEach(viewContext.delete)

do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}

save() :

The save() function is responsible for saving data to Core Data. Here's a breakdown of what it does:

  1. It creates a new instance of the Person entity using the Person class and assigns it to the newPerson constant.
  2. The properties of the newPerson instance, such as name, age, and address, are set based on the values of the name, age, and address variables.
  3. The viewContext is used to save the changes made to the managed object context. If an error occurs during the save operation, a fatal error is triggered, displaying the error message and its associated user info.
  4. After successfully saving the changes, the name, age, and address variables are reset to empty strings.

delete(offsets: IndexSet) :

The delete(offsets: IndexSet) function is responsible for deleting data from Core Data. Here's a breakdown of what it does:

  1. It maps the offsets parameter, which represents the indices of the items to be deleted, to the corresponding Person objects in the people array.
  2. The viewContext is used to delete each of the Person objects obtained in the previous step.
  3. After deleting the objects, the viewContext is used to save the changes made to the managed object context. If an error occurs during the save operation, a fatal error is triggered, displaying the error message and its associated user info.

Step 10: Make your View like this example, which is my View

import SwiftUI
import CoreData

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: [])
private var people: FetchedResults<Person>

@State private var name = ""
@State private var age = ""
@State private var address = ""

var body: some View {
NavigationView {
VStack {
Form {
Section(header: Text("Person Information")) {
TextField("Name", text: $name)
TextField("Age", text: $age)
.keyboardType(.numberPad) // Assuming age is a number
TextField("Address", text: $address)
}

Section {
Button("Save") {
savePerson()
}
}
}
.navigationTitle("Add Person")

List {
ForEach(people) { person in
VStack(spacing: 10) {
Text("Name: \(person.name)").frame(maxWidth: .infinity, alignment: .leading)
Text("Age: \(person.age)").frame(maxWidth: .infinity, alignment: .leading)
Text("Address: \(person.address)").frame(maxWidth: .infinity, alignment: .leading)
}
}
.onDelete(perform: deletePerson)
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("People")
.navigationBarTitleDisplayMode(.inline)
}
}
}
// Add Person
private func savePerson() {
withAnimation {
let newPerson = Person(context: viewContext)
newPerson.name = name
newPerson.age = Int64(age) ?? 0
newPerson.address = address

do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}

// Reset fields after saving
name = ""
age = ""
address = ""
}
}
// delete Person
private func deletePerson(offsets: IndexSet) {
withAnimation {
offsets.map { people[$0] }.forEach(viewContext.delete)

do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}


struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Step 11: Add a Data Manager to your main project

import SwiftUI

@main
struct CoreDataExampleApp: App {
@StateObject private var manager: DataManager = DataManager()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(manager)
.environment(\.managedObjectContext, manager.container.viewContext)
}
}
}

App Struct :

The code defines a struct called “CoreDataExampleApp” that conforms to the App protocol. This struct represents the main entry point of the app:

@main
struct CoreDataExampleApp: App {

State Object:

Inside the CoreDataExampleApp struct, there is a @StateObject property called manager of type DataManager. The @StateObject attribute ensures that the manager instance is retained as long as the app is running:

@StateObject private var manager: DataManager = DataManager()

Body :

The body property is a computed property that returns a Scene:

var body: some Scene {

WindowGroup :

Inside the WindowGroup scene, the ContentView is displayed as the root view. The ContentView is configured with the manager instance as an environment object and the viewContext of the manager instance as the managed object context environment value:

WindowGroup {
ContentView()
.environmentObject(manager)
.environment(\.managedObjectContext, manager.container.viewContext)
}

That’s the breakdown of the code you provided. It sets up the main entry point of the SwiftUI app, creates an instance of the DataManager class as a state object, and configures the ContentView with the necessary environment objects and managed object context

Result :

For the best of luck and success, Thanks!

--

--