What you’ll build
You’ll start with a simple TODO app that keeps its items only in memory, then add Core Data so those items remain available after the app closes and launches again.
Use the original SwiftUI TODO project and follow the steps below.
Create the Core Data model
In Xcode, choose File → New → File. Filter the file types by “data model,” select Data Model, and click Next. Keep the .xcdatamodeld extension and save the file.
Run the project without Core Data
Run the app in the simulator and add a TODO item. You can mark the task as complete, but after terminating and relaunching the app, the TODO items are gone because they are stored only in memory.
Next, you’ll configure the data model so those changes can persist.
Configure Model.xcdatamodeld
Select the Model.xcdatamodeld file you created, click Add Entity, and rename the entity to Todo.
Add two attributes to the Todo entity: taskName with type String, and isCompleted with type Boolean.
Update your DataManager
Open DataManager.swift, import CoreData, and create an NSPersistentContainer using the name of your model.
import CoreData
import Foundation
/// Main data manager to handle the todo items
class DataManager: NSObject, ObservableObject {
@Published var todoItems: [TodoItem] = [TodoItem]()
/// Core Data container using Model.xcdatamodeld
let container: NSPersistentContainer = NSPersistentContainer(name: "Model")
}View original example on GitHub Gist →
Then load the persistent stores when the data manager is initialized.
import CoreData
import Foundation
class DataManager: NSObject, ObservableObject {
@Published var todoItems: [TodoItem] = [TodoItem]()
let container: NSPersistentContainer = NSPersistentContainer(name: "Model")
override init() {
super.init()
container.loadPersistentStores { _, _ in }
}
}View persistent-store example on GitHub Gist →Inject the managed object context into SwiftUI
Open your @main application file and pass the Core Data container’s view context into the SwiftUI environment.
import SwiftUI
@main
struct TodoListApp: App {
@StateObject private var manager: DataManager = DataManager()
var body: some Scene {
WindowGroup {
ListContentView()
.environmentObject(manager)
.environment(\.managedObjectContext, manager.container.viewContext)
}
}
}View original example on GitHub Gist →Explore complete iOS app projects
Once your SwiftUI app has a working persistence layer, you can apply the same architecture to larger projects. These complete iOS app templates offer practical codebases to customize and extend.
Add FetchRequest to the SwiftUI view
In ListContentView, access the managed object context and create a FetchRequest that loads Todo entities from Core Data.
import SwiftUI
struct ListContentView: View {
@EnvironmentObject var manager: DataManager
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(sortDescriptors: []) private var todoItems: FetchedResults<Todo>
// ...
}View original example on GitHub Gist →Use FetchRequest data instead of DataManager
Replace the in-memory manager.todoItems collection with the todoItems FetchRequest. Core Data-backed changes will then update the SwiftUI view through the FetchRequest.
var body: some View {
NavigationView {
List {
ForEach(todoItems) { item in
Label(
item.taskName ?? "No Name",
systemImage: "circle\(item.isCompleted ? ".fill" : "")"
)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.onTapGesture {
item.isCompleted = !item.isCompleted
}
}
}
.navigationTitle("TODO")
.navigationBarItems(trailing: Button(action: addItem) {
Image(systemName: "plus")
})
}
}View original example on GitHub Gist →Save new items to Core Data
Update your existing addItem method so new tasks are created as Todo entities in the managed object context rather than being appended to the in-memory DataManager array.
private func addItem() {
presentTextInputAlert(title: "Add Task", message: "Enter your task name") { name in
let newTask = Todo(context: viewContext)
newTask.taskName = name
try? viewContext.save()
}
}View original example on GitHub Gist →FAQ
Why does a completed task appear incomplete after relaunching the app?
Changing the fetched object updates it in memory, but you also need to save the managed object context after changing isCompleted.
.onTapGesture {
item.isCompleted = !item.isCompleted
try? viewContext.save()
}View original save example on GitHub Gist →Where can I get the complete source code?
Following the tutorial first is the best way to understand each Core Data change. When you’re ready, you can download the completed project with all of the changes from this guide.
The finished SwiftUI TODO example.