Core Data in SwiftUI
Core data uses persistent data storage to save an application’s information onto the device itself, that is, the data is available offline.
Step 1: Create data model
- Create a data model file that will store our entities and attributes

- Add entities and attributes. Given e.g below is a boolean value which is not optional and has “No” as the default value.

Step 2: Load model and actual data
-
Create a data controller file of type swift file.

-
Create a class DataController of type ObservableObject
-
Initialise a container variable to NSPersistentContainer
NSPersistentContainer is responsible for loading a data model and give us access to entities and attributes.
The NSPersistentContainer take as parameter the data model file created in step 1 rather than the entity file.
import CoreData
import Foundation
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "MealModel")
}
- Load the actual data
To have access to the actual data store in the user’s device, we call loadPersistentStore on our container
The loadPersistentStore takes as handler: description and error. This is used to display any kind of error encountered when loading the data.
init() {
container.loadPersistentStores { description, error in
if let error = error {
print("Failed to load data: \(error.localizedDescription)")
}
}
Step 3: Preparation to use in SwiftUI
Apps work with only one core data store at a time. Rather than each view creating their own store, our app creates it once when the app starts, stores it in swiftUI environment and make it accessible everywhere in our app.
We first create an instance of DataController and introduce it into our SwiftUI environment.
In the main file
struct MealCoreData: App {
// Creating instance
@StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
// Introduction in environment
ContentView().environment(\.managedObjectContext, dataController.container.viewContext)
}
}
}
Tip: if using previews, managed object context should be injected into the preview struct for that particular view.
Special Note: Managed Object Context deals with live data. I.e it only exist in Ram as long as the app is running, once the app closes it loses its data. Therefore to store it permanently, it needs to be saved into the persistent store.
Advantage: Using live data is less time consuming and save resources as it foregoes the need of reading from and writing to disk constantly. Instead, it only does it once the data is actually saved.
Step 4: CRUD
Reading from and writing to core data is done through FetchRequest.
FetchRequest takes at least one parameter that describe how we want the data to be sorted.
@FetchRequest(sortDescriptors: []) var meals: FetchedResults
That creates a fetch request with no sorting, and places it into a property called meals that has the type FetchedResults.
Now meals can be manipulated as a simple swift array.
Let’s create a simple UI for meal, so we can perform CRUD.
struct ContentView: View {
let title = ["Burger", "Spicy Burger", "Pizza", "Spaghetti", "Chicken BBQ", "Lamb chop", "Chop suey", "Soya beans", "Baked beans", "Plain rice"]
@FetchRequest(sortDescriptors: []) var meals: FetchedResults
var body: some View {
VStack {
List(meals) { meal in
Text(meal.title ?? "Unknown")
}
Button {
let chosenMeal = title.randomElement()!
} label: {
Text("Add Meal")
}
}// VStack
}
}
As mentioned earlier, Managed Object Context(MOC) is used to save/delete data from store. So, to use MOC we need to declare it:
@Environment(\.managedObjectContext) var moc
Now let’s try using moc to save the data we declared and selected.
Button {
let chosenMeal = set.randomElement()
let meal = Meal(context: moc)
meal.title = "\(chosenMeal ?? "Unknown")"
try? moc.save()
} label: {
Text("Add Meal")
}
Special Note: Each view using moc need to be added to the environment.
Filtering core data
We have a model entity called Beverage, which have as attributes title and categories both of type String.
Step 1: Create the MOC environment in the view
@Environment(\.managedObjectContext) var moc
Step 2: Read data from model using fetch request
@FetchRequest(sortDescriptors: [], predicate: nil) var Beverages: FetchedResults
Step 3: Create view to write data and save to MOC
var body: some View {
VStack {
List(Beverages, id:\.self) { beverage in
HStack {
Text(beverage.title ?? "Unknown name")
.font(.title3)
Spacer()
Text(beverage.categories ?? "Unknown")
.font(.body)
.foregroundColor(.gray)
}//HStack
} //List
Button("Add Examples") {
let beverage1 = Beverage(context: moc)
beverage1.title = "Coffee"
beverage1.categories = "Hot"
let beverage2 = Beverage(context: moc)
beverage2.title = "Peach QQ"
beverage2.categories = "Cold"
let beverage3 = Beverage(context: moc)
beverage3.title = "Sprite"
beverage3.categories = "Fizzy"
let beverage4 = Beverage(context: moc)
beverage4.title = "Water"
beverage4.categories = "Room temp"
// Save to database
try? moc.save()
}//Button
}//Vstack
}//body

No sorting
Step 4: Learn how to filter data using NSPredicate property wrapper
- Filter data where categories = cold
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "categories == 'Cold'")) var Beverages: FetchedResults

Filtered data
- Filtering data where title = pimm’s cup
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "title == 'pimm's'")) var Beverages: FetchedResults
This results in

WHY THIS ERROR?
Because there is an ‘ in the beverage’s name
To be able to parse string data that contain ‘ we do it as follows:
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "title == %@", "Pimm's Cup")) var Beverages: FetchedResults
the ‘%@’ represents → whatever the string inserted here and the string inserted here is written as the second argument of NSPredicate
Price added to attribute so we can provide an example of using comparison operators for sorting
// ... more code here
beverage1.price = 110
beverage2.price = 175
beverage3.price = 30
beverage4.price = 20
beverage5.price = 100
beverage6.price = 275
beverage7.price = 275
// try? moc.save()

No sorting
- Sorting beverages whose prices are less and equal to 100
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "price <= %@", "100")) var Beverages: FetchedResults

- Sorting beverages that can be in different categories
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "categories IN %@", ["Alcohol based", "Cocktail"])) var Beverages: FetchedResults

- Sorting beverages with title starting with a particular character
- Case sensitive
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "title BEGINSWITH %@", "P")) var Beverages: FetchedResults

- Case insensitive
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "title BEGINSWITH[c] %@", "c")) var Beverages: FetchedResults

- Sorting by title containing case insensitive character c
@FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "title CONTAINS[c] %@", "c")) var Beverages: FetchedResults

Filtering a search bar using FetchRequest
struct ViewSix: View {
@State private var searchText = ""
@Environment(\.managedObjectContext) var moc
@FetchRequest(sortDescriptors: []) var beverages: FetchedResults
var body: some View {
VStack{
List {
ForEach(beverages) { beverage in
Text(beverage.title ?? "" )
}
.listRowSeparator(.hidden)
}
.listStyle(.plain)
}//Vstack
.navigationTitle("Beverages")
.searchable(text: $searchText)
.onChange(of: searchText, perform: { searchText in
if searchText.isEmpty {
print("Empty")
} else {
beverages.nsPredicate = NSPredicate(format: "title CONTAINS[c] %@", searchText)
}//else
})//OnChange
}//body
}//ViewSix
