SwiftData Basics

Reviewed & published by Brayan K

SwiftData (iOS 17+) is Apple's modern, code-first persistence framework. You'll define models with @Model, set up a ModelContainer, fetch with @Query, insert and delete objects, and see how it improves on Core Data.

Part of the free Swift course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn in This Lesson

📊 SwiftData Building Blocks

PieceRole
@ModelMakes a class persistent
ModelContainerSets up storage
ModelContextInsert / delete workspace
@QueryFetch and observe models

1️⃣ Define a Model with @Model

A SwiftData model is just a class with the @Model macro. The macro adds persistence and change tracking — no separate model file, no setValue(forKey:).

import SwiftData
import Foundation

// @Model turns a plain Swift class into a persistent SwiftData model.
// No .xcdatamodeld file and no boilerplate — the macro does the work.
@Model
final class Task {
    var title: String
    var isDone: Bool
    var createdAt: Date

    init(title: String, isDone: Bool = false) {
        self.title = title
        self.isDone = isDone
        self.createdAt = Date()
    }
}

let task = Task(title: "Learn SwiftData")
print(task.title)   // Learn SwiftData

2️⃣ Set Up the Container

Attach a ModelContainer once at the app root with .modelContainer(for:). Views then read the active ModelContext from the environment.

import SwiftUI
import SwiftData

// A ModelContainer wires up storage. Attach it once at the app root.
@main
struct TodoApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // Every model type you persist is listed here.
        .modelContainer(for: Task.self)
    }
}

// Inside a view you reach the context from the environment.
struct ContentView: View {
    @Environment(\.modelContext) private var context
    var body: some View { Text("Ready") }
}

Your turn. Fill in the macro and the model type.

// 🎯 YOUR TURN — replace each ___ then run in Xcode (iOS 17+).
import SwiftData

___                              // 👉 the macro that makes this persistent
final class Note {
    var text: String
    init(text: String) { self.text = text }
}

// Attach storage at the app root:
//   .modelContainer(for: ___.self)   👉 the model type to persist

// ✅ Once filled in, Note becomes a SwiftData model with its own storage.

3️⃣ Query, Insert, and Delete

@Query fetches models with type-safe sorting and keeps the view in sync. Use context.insert(_:) to add and context.delete(_:) to remove.

import SwiftUI
import SwiftData

// @Query fetches models and keeps the view in sync as data changes.
struct TaskList: View {
    @Environment(\.modelContext) private var context

    // Sort and filter declaratively right in the property.
    @Query(sort: \Task.createdAt, order: .reverse) private var tasks: [Task]

    var body: some View {
        List {
            ForEach(tasks) { task in
                Text(task.title)
            }
        }
    }

    func add() {
        context.insert(Task(title: "New task"))   // insert a new object
    }

    func remove(_ task: Task) {
        context.delete(task)                       // delete an object
    }
}

Now you try. Fill in the insert and delete methods.

// 🎯 YOUR TURN — insert then delete.
import SwiftData

@Model final class Item { var name: String; init(name: String) { self.name = name } }

func demo(context: ModelContext) {
    let item = Item(name: "Pen")
    context.___(item)     // 👉 the method that adds the object to the store
    context.___(item)     // 👉 the method that removes the object
}

// ✅ Once filled in: the item is inserted, then deleted.

Pro Tips

Common Errors (and the fix)

📋 Quick Reference

TaskCodeResult
Model@Model final class Taskpersistent
Storage.modelContainer(for: Task.self)sets up store
Fetch@Query var tasks: [Task]live results
Addcontext.insert(task)new object
Removecontext.delete(task)removed

Mini-Challenge: SwiftData To-Do

Build a small @Model-backed to-do list with @Query, insert, and delete — and notice how little code it takes.

// 🎯 MINI-CHALLENGE: A tiny SwiftData to-do
// 1. Define an @Model class 'Todo' with title: String and done: Bool.
// 2. In a view, use @Query(sort: \Todo.title) to list all todos.
// 3. Add a button that calls context.insert(Todo(...)) to add one.
// 4. Add swipe-to-delete that calls context.delete(todo).
//
// Reflect: how much less boilerplate is this than the Core Data version?

// your code here

🎉 Lesson Complete!

Practice quiz

Which macro marks a class as a SwiftData model?

  • @State
  • @Model
  • @Entity
  • @Persisted

Answer: @Model. @Model turns a class into a persistent SwiftData model.

From which iOS version is SwiftData available?

  • iOS 13
  • iOS 15
  • iOS 17
  • iOS 11

Answer: iOS 17. SwiftData was introduced with iOS 17.

What sets up SwiftData's storage for your app?

  • NSPersistentContainer
  • ModelContainer
  • URLSession
  • ModelView

Answer: ModelContainer. A ModelContainer configures and holds SwiftData's storage.

Which property wrapper fetches and observes model objects in a view?

  • @FetchRequest
  • @Query
  • @Published
  • @Binding

Answer: @FetchRequest. @Query loads SwiftData models and keeps the view updated.

Which context method adds a new model object?

  • context.insert(object)
  • context.add(object)
  • context.append(object)
  • context.push(object)

Answer: context.insert(object). modelContext.insert(_:) inserts a new object into the store.

Which context method removes a model object?

  • context.remove(object)
  • context.discard(object)
  • context.drop(object)
  • context.delete(object)

Answer: context.delete(object). modelContext.delete(_:) removes the object.

Compared with Core Data, SwiftData models are defined using...

  • An .xcdatamodeld editor
  • Plain Swift classes with @Model
  • JSON files
  • Property lists

Answer: Plain Swift classes with @Model. SwiftData uses code-first @Model classes instead of a visual model editor.

How do you reach the model context inside a SwiftUI view?

  • @State var context
  • @Environment(\.modelContext)
  • URLSession.shared
  • @Binding context

Answer: @Environment(\.modelContext). @Environment(\.modelContext) provides the active context to the view.

What is SwiftData built on top of?

  • Realm
  • Core Data's proven engine
  • SQLite only, no framework
  • UserDefaults

Answer: Core Data's proven engine. SwiftData is a modern Swift API layered over Core Data's engine.

A key advantage of SwiftData over Core Data is...

  • It needs Objective-C
  • It cannot persist data
  • It only works on macOS
  • A type-safe, code-first Swift API with less boilerplate

Answer: A type-safe, code-first Swift API with less boilerplate. SwiftData offers a cleaner, type-safe Swift-native API with far less boilerplate.

Continue this course

Frequently asked questions

What does the @Model macro do?

@Model attaches to a Swift class and turns it into a persistent SwiftData model. The macro generates the storage and change-tracking code for you at compile time, so you write a plain class with ordinary properties and an initializer. There is no .xcdatamodeld file to maintain, which is the biggest day-to-day difference from Core Data.

What is a ModelContainer and where does it go?

A ModelContainer sets up SwiftData's underlying storage for the model types you list. In a SwiftUI app you usually attach it once at the app root with the .modelContainer(for:) modifier, passing the model types to persist. From there, views read the active ModelContext out of the environment to insert, delete, and save objects.

How is @Query different from @FetchRequest?

Both fetch persistent objects and keep a SwiftUI view in sync, but @Query is the SwiftData equivalent and is more concise. You declare sorting and filtering with type-safe key paths, for example @Query(sort: \Task.createdAt), and it returns a plain Swift array of your model type. @FetchRequest is the older Core Data wrapper using NSSortDescriptor and FetchedResults.

How do I add and remove objects in SwiftData?

Get the ModelContext from the environment, then call context.insert(object) to add a new model and context.delete(object) to remove one. SwiftData tracks these changes and persists them, and any @Query observing that data refreshes the view automatically. SwiftData also autosaves in common setups, so you often do not call save() manually.

Should I use SwiftData or Core Data?

SwiftData is the modern, Swift-first choice for new apps targeting iOS 17 and later: it offers a type-safe, code-first API built on Core Data's proven engine with far less boilerplate. Choose Core Data when you must support older OS versions, need advanced features SwiftData does not yet expose, or are maintaining an existing Core Data codebase.