Hello fellow SwiftUI learner,
I hope you are doing well, spring is on the horizon and people in Sweden are starting to leave their houses and actually go out for some fresh air. 10+ degrees celsius and people are starting to use shorts already.
I have decided to start a new CoreData series here on my newsletter as it is something I have had to learn when building my new app. Previously I used UserDefaults for it but heard that CoreData is a better way to manage data in your apps. From what I understand, CoreData is better for handling more complex data structures and you use UserDefaults for smaller and simpler apps.
I think the best way for you is to just ask chatGPT if you wanna dig deeper into it.
Anyhow, this is what we will do today.
Creating a new project with CoreData
Go through the persistence controller
Setting up an entity
Setting up the CoreData environment
Start Xcode, press create a new project, select app and then name the project whatever you like. Make sure to change the storage to CoreData as in the picture below. Then press next.
You should now see your ContentView filled with some code. The default code you get when creating a new project is a good example of what you can do. In this scenario we will not use the ContentView() file at first so you can ignore that file for now. Instead you should have a file call Persistence(), and here you find the PersistenceController struct.
The code in the file should be like this:
struct PersistenceController {
static let shared = PersistenceController()
@MainActor
static let preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreDataProject")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
You don’t have to do anything with this code as we will use it later.
Now we need to set up our Entity. An entity is a blueprint for the type of data your app stores, similar to a table in a relational database. An entity represents an object in your data model. It defines the structure of the data you want to store. So for this little project I want to store information about people, why people you ask. To be honest I don’t know but that is the best I can do for now. You can choose whatever data you like.
When you created your project a CoreData file was created, press it and you will see this view. Mine is called CoreDataProject.
This is the default entity that is set up for you when you create your project. But don’t worry, we will change the name of the entity and some more attributes. To change the name of the entity, just long press Item and change it to something you like. I did this and changed the entity name to Person and the Date type in the attributes list to birthday. See picture below.
If you try and run your project now, your build will fail as we have changed the name of our entity. So we need to fix that, since we don’t need the code in ContentView, we can delete all code and just leave it like this for now:
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Person.birthday, ascending: true)],
animation: .default)
private var items: FetchedResults<Person>
var body: some View {
VStack {
}
}
}
#Preview {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
What we have done now is to replace Item with Person in the items variable and we also had to call Person.birthday in sortDescriptors now as well. Both changes written with bold text in the code above.
We also get an error in our PersistenceController, this is also due to the fact that we have changed the name of the default entity that Xcode set up for us. Change the code to the following:
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
@MainActor
static let preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Person(context: viewContext)
newItem.birthday = Date()
}
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "CoreDataProject")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
You should now be able to run your project. As you can notice, nothing is showing up as we have removed most of the code in the ContentView() file. So that is what we are going to do in the next post so stay tuned for part 2 of this series.
Then we will start building some views.
Have a great day!
/MrSwiftUI