Skip to content
axiom-sqlitedata-migration logo

Migrating from SwiftData to SQLiteData

axiom-sqlitedata-migration

Use when migrating from SwiftData to SQLiteData — decision guide, pattern equivalents, code examples, CloudKit sharing (SwiftData can't), performance benchmarks, gradual migration strategy

SKILL.md

Full skill instructions

Migrating from SwiftData to SQLiteData

When to Switch

┌─────────────────────────────────────────────────────────┐
│ Should I switch from SwiftData to SQLiteData?           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Performance problems with 10k+ records?                │
│    YES → SQLiteData (10-50x faster for large datasets)  │
│                                                         │
│  Need CloudKit record SHARING (not just sync)?          │
│    YES → SQLiteData (SwiftData cannot share records)    │
│                                                         │
│  Complex queries across multiple tables?                │
│    YES → SQLiteData + raw GRDB when needed              │
│                                                         │
│  Need Sendable models for Swift 6 concurrency?          │
│    YES → SQLiteData (value types, not classes)          │
│                                                         │
│  Testing @Model classes is painful?                     │
│    YES → SQLiteData (pure structs, easy to mock)        │
│                                                         │
│  Happy with SwiftData for simple CRUD?                  │
│    YES → Stay with SwiftData (simpler for basic apps)   │
│                                                         │
└─────────────────────────────────────────────────────────┘

Pattern Equivalents

SwiftDataSQLiteData
@Model class Item@Table nonisolated struct Item
@Attribute(.unique)@Column(primaryKey: true) or SQL UNIQUE
@Relationship var tags: [Tag]var tagIDs: [Tag.ID] + join query
@Query var items: [Item]@FetchAll var items: [Item]
@Query(sort: \.title)@FetchAll(Item.order(by: \.title))
@Query(filter: #Predicate { $0.isActive })@FetchAll(Item.where(\.isActive))
@Environment(\.modelContext)@Dependency(\.defaultDatabase)
context.insert(item)Item.insert { Item.Draft(...) }.execute(db)
context.delete(item)Item.find(id).delete().execute(db)
try context.save()Automatic in database.write { } block
ModelContainer(for:)prepareDependencies { $0.defaultDatabase = }

Code Example

SwiftData (Before)

import SwiftData

@Model
class Task {
    var id: UUID
    var title: String
    var isCompleted: Bool
    var project: Project?

    init(title: String) {
        self.id = UUID()
        self.title = title
        self.isCompleted = false
    }
}

struct TaskListView: View {
    @Environment(\.modelContext) private var context
    @Query(sort: \.title) private var tasks: [Task]

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

    func addTask(_ title: String) {
        let task = Task(title: title)
        context.insert(task)
    }

    func deleteTask(_ task: Task) {
        context.delete(task)
    }
}

SQLiteData (After)

import SQLiteData

@Table
nonisolated struct Task: Identifiable {
    let id: UUID
    var title = ""
    var isCompleted = false
    var projectID: Project.ID?
}

struct TaskListView: View {
    @Dependency(\.defaultDatabase) var database
    @FetchAll(Task.order(by: \.title)) var tasks

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

    func addTask(_ title: String) {
        try database.write { db in
            try Task.insert {
                Task.Draft(title: title)
            }
            .execute(db)
        }
    }

    func deleteTask(_ task: Task) {
        try database.write { db in
            try Task.find(task.id).delete().execute(db)
        }
    }
}

Key differences:

  • class → struct with nonisolated
  • @Model → @Table
  • @Query → @FetchAll
  • @Environment(\.modelContext) → @Dependency(\.defaultDatabase)
  • Implicit save → Explicit database.write { } block
  • Direct init → .Draft type for inserts
  • @Relationship → Explicit foreign key + join

CloudKit Sharing (SwiftData Can't Do This)

SwiftData supports CloudKit sync but NOT sharing. SQLiteData is the only Apple-native option for record sharing.

// 1. Setup SyncEngine with sharing
prepareDependencies {
    $0.defaultDatabase = try! appDatabase()
    $0.defaultSyncEngine = try SyncEngine(
        for: $0.defaultDatabase,
        tables: Task.self, Project.self
    )
}

// 2. Share a record
@Dependency(\.defaultSyncEngine) var syncEngine
@State var sharedRecord: SharedRecord?

func shareProject(_ project: Project) async throws {
    sharedRecord = try await syncEngine.share(record: project) { share in
        share[CKShare.SystemFieldKey.title] = "Join my project!"
    }
}

// 3. Present native sharing UI
.sheet(item: $sharedRecord) { record in
    CloudSharingView(sharedRecord: record)
}

Sharing enables: Collaborative lists, shared workspaces, family sharing, team features.


Performance Comparison

OperationSwiftDataSQLiteDataImprovement
Insert 50k records~4 minutes~45 seconds5x
Query 10k with predicate~2 seconds~50ms40x
Memory (10k objects)~80MB~20MB4x smaller
Cold launch (large DB)~3 seconds~200ms15x

Benchmarks approximate, vary by device and data shape.


Gradual Migration Strategy

You don't have to migrate everything at once:

  1. Add SQLiteData for new features — Keep SwiftData for existing simple CRUD
  2. Migrate one model at a time — Start with the performance bottleneck
  3. Use separate databases initially — SQLiteData for heavy data/​sharing, SwiftData for preferences
  4. Consolidate if needed — Or keep hybrid if it works

Common Gotchas

Relationships → Foreign Keys

// SwiftData: implicit relationship
@Relationship var tasks: [Task]

// SQLiteData: explicit column + query
// In child: var projectID: Project.ID
// To fetch: Task.where { $0.projectID == project.id }

Cascade Deletes

// SwiftData: @Relationship(deleteRule: .cascade)

// SQLiteData: Define in SQL schema
// "REFERENCES parent(id) ON DELETE CASCADE"

No Automatic Inverse

// SwiftData: @Relationship(inverse: \Task.project)

// SQLiteData: Query both directions manually
let tasks = Task.where { $0.projectID == project.id }
let project = Project.find(task.projectID)

Related Skills:

  • axiom-sqlitedata — Full SQLiteData API reference
  • axiom-swiftdata — SwiftData patterns if staying with Apple's framework
  • axiom-grdb — Raw GRDB for complex queries

History: See git log for changes

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View