Mobile Development4 min read

SwiftData

Apple's modern Swift-first persistence framework introduced in iOS 17 - replaces Core Data boilerplate with @Model macros, @Query property wrappers, and automatic CloudKit sync

iOS 17AppleSwiftPersistenceSwiftUI
---
name: swiftdata
description: >
  You are a SwiftData developer. You define @Model classes, query them with @Query in SwiftUI views, handle schema migrations with VersionedSchema, and sync with CloudKit via ModelContainer.
---

SwiftData is Apple's replacement for Core Data, available on iOS 17+. It uses Swift macros to eliminate the NSManagedObject subclass, NSFetchRequest, and NSPersistentContainer boilerplate. If your minimum deployment target is iOS 17, start with SwiftData - the same SQLite engine powers both, and migration from SwiftData to Core Data is possible if you need features SwiftData does not yet expose.

Workflow

  1. Annotate your model classes with @Model and mark relationships with @Relationship@Model generates all NSManagedObject machinery at compile time; forgetting @Relationship on a property that references another model produces a non-persisted transient property that disappears on launch
  2. Create the ModelContainer once in your App struct using .modelContainer(for:) modifierCreating multiple ModelContainers for the same model in the same process produces separate stores that diverge; the .modelContainer modifier shares the container across the entire view hierarchy
  3. Use @Query in SwiftUI views for reactive data fetching - never fetch manually in onAppear@Query observes the model context and automatically triggers view updates when matching objects change; onAppear fetches only once and misses subsequent changes from background operations
  4. Perform background writes via ModelContext.perform on a detached actorThe @MainActor ModelContext must not block the main thread; background inserts and deletes run on a background ModelContext created from the same ModelContainer and merge automatically
  5. Define a VersionedSchema and migration plan before shipping any schema changeShipping a model change without a migration plan causes SwiftData to fail silently (iOS 17.0-17.2) or throw a fatal error - the user's data is silently destroyed or the app crashes

Output Format

A production SwiftData model uses @Model with explicit relationship cascade rules, and is queried via @Query with a Predicate and SortDescriptor in the SwiftUI view.

@Model
final class Item {
  var id: UUID
  var name: String
  var createdAt: Date
  @Relationship(deleteRule: .cascade) var tags: [Tag]
  init(name: String) {
    self.id = UUID()
    self.name = name
    self.createdAt = .now
    self.tags = []
  }
}

struct ItemsView: View {
  @Query(sort: \.createdAt, order: .reverse) private var items: [Item]
  @Environment(\.modelContext) private var context
}

Examples

@Query with predicate filter

Input

Show only items created in the last 7 days

Output

@Query(filter: #Predicate<Item> { $0.createdAt > Date.now.addingTimeInterval(-604800) }, sort: \.createdAt) var recentItems: [Item]

Wrong: fetching in onAppear

Input

Loading items in .onAppear { items = try context.fetch(FetchDescriptor<Item>()) }

Output

Fetches only once at view appearance; changes made by background operations or other views are not reflected until the view disappears and reappears

Right: @Query for reactive data

Input

Same items list

Output

@Query var items: [Item] - SwiftData observes the store and calls body whenever a matching Item is inserted, updated, or deleted

Edge Cases & Gotchas

  • SwiftData and CloudKit sync requires all properties to have default values and all relationships to be optional - CloudKit cannot merge non-optional missing values
  • @Model classes must be final - SwiftData's macro-generated observation does not support inheritance; use composition with embedded value types for shared behavior
  • Predicate compilation failures - #Predicate only supports a subset of Swift expressions that can be translated to SQLite predicates; custom functions, computed properties, and closures in predicates cause compile errors

When to Break These Rules

  • Use Core Data when your minimum deployment target is below iOS 17, or when you need features SwiftData does not yet expose: NSFetchedResultsController, NSBatchInsertRequest for large imports, or custom migration mappings
  • Use GRDB.swift when you need raw SQL queries, full-text search, or custom SQLite extensions that neither SwiftData nor Core Data expose

Reference Files

  • src/data/skills/index.js - add skill entries here
  • src/components/Skills/DocRenderer.js - renders this doc format

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.