iOS Tutorial

Add Core Data to an Existing SwiftUI Project

Add persistent storage to an existing SwiftUI app by creating a Core Data model, loading it with NSPersistentContainer, and connecting it to your SwiftUI views.

SwiftUICore DataXcodePersistence

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.

Start with the sample project

Use the original SwiftUI TODO project and follow the steps below.

Download project
Step 1

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.

Step 2

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.

Step 3

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.

Step 4

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 →
Step 5

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 →
iOS App Templates

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.

Step 6

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 →
Step 7

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 →
Step 8

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 →
Test persistence: run the app, add a few tasks, terminate it, and launch it again. Your saved Core Data items should remain.

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.

Completed Core Data project

The finished SwiftUI TODO example.

Download source

Need help building your iOS app?

Apps4World provides custom native iOS development in Swift, SwiftUI, and UIKit — from the first screen through App Store delivery.

Discuss your project