Quick guide to using Core Data with SwiftUI

Learn the basics for using the Core Data framework with SwiftUI in iOS

20 Mar 2021 · 8 min read

When it comes to persisting complex data structures in iOS, Apple provides us with the Core Data framework. With Core Data, we can model entities and their relationships, save data for offline use, add undo functionality and more.

The easiest way to see how Core Data works with SwiftUI is by creating a new SwiftUI project and selecting the Use Core Data checkmark. Xcode will generate a working example that we can try out and review immediately.

New Core Data with SwiftUI project New SwiftUI project using Core Data

Let's go through it step by step.

The Core Data Stack

Before we jump into the example, let's recap the most important aspects about Core Data. The Core Data stack consists of:

The NSPersistentContainer provides a way to set up the entire Core Data stack. All we need to do is to create an instance of NSPersistentContainer and call loadPersistentStores on it.

let container = NSPersistentContainer(name: "AppName")
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Error: \(error.localizedDescription)")
}
}
return container

This setup can be found in the PersistenceController of the example project.

The Data Model

The example project comes with an AppName.xcdatamodel file where we can model our data objects, including their types, properties and relationships.

Core Data Model Core Data Model

The Core Data model has an entity called Item with a Date property called timestamp. It's a very basic setup, but enough to demostrate how Core Data works with SwiftUI. So let's jump in.

Using Core Data with SwiftUI

After initializing the Core Data stack, it can be injected into SwiftUI views.

struct CoreDataWithSwiftUIExampleApp: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}

As we can see above, the managed object context is injected into the ContentView by using the environment() modifier.

After this is done, ContentView now has access to the managed object context to save, update or delete items.

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
// ...
}

🔗 Further reading: @Environment property wrapper in SwiftUI.

Fetching data

To request items from the Core Data store, SwiftUI provides a FetchRequest property wrapper.

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
List {
ForEach(items) { item in
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
}
}
}
}

As we can see above, ContentView has an items property that holds the Itemresults of the FetchRequest. The items are sorted by it's timestamp property. This property is used by the view to fill the List with content. The List will be updated automatically if an Item is added, deleted or updated.

🔗 If you like to dive deeper into fetch requests, check out this article: @FetchRequest property wrapper in SwiftUI.

Adding new items

For adding new items to the Core Data store, the ContentView has a method called addItem.

private func addItem() {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
// Error handling
}
}

It initializes a new Item entity, sets it's attributes and calls save() on the managed object context. The addItem method is called every time the plus button is tapped.

ToolbarItem(placement: .navigationBarTrailing) {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}

Deleting items

Deleting items works similar to adding items.

private func deleteItems(offsets: IndexSet) {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Error handling
}
}

This method is connected to the view with the onDelete modifier provided by the ForEach structure in the List.

List {
ForEach(items) { item in
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
}
.onDelete(perform: deleteItems)
}

It is called every time elements are deleted in the view. SwiftUI passes a set of indices to the closure that are used to delete items.

Conclusion

Thanks to SwiftUI, getting started with Core Data has never been easier. Of course, Core Data is a huge topic and there is a lot more to learn, but I hope this guide was a good starting point for your Core Data journey.