SwiftData - Modern Data Persistence

Apple's declarative data modeling framework for Swift applications

🎯 What is SwiftData?

SwiftData is Apple's modern replacement for Core Data, providing:

  • Declarative syntax with Swift macros
  • Type safety at compile time
  • Automatic CloudKit sync capabilities
  • SwiftUI integration out of the box

🚀 Getting Started

Basic Model Definition

import SwiftData

@Model
class Task {
    var title: String
    var isCompleted: Bool
    var createdAt: Date
    var priority: Priority
    
    init(title: String, priority: Priority = .medium) {
        self.title = title
        self.isCompleted = false
        self.createdAt = Date()
        self.priority = priority
    }
}

enum Priority: String, Codable, CaseIterable {
    case low, medium, high
}

App Setup

import SwiftUI
import SwiftData

@main
struct TaskApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Task.self)
    }
}

📱 SwiftUI Integration

Querying Data

struct TaskListView: View {
    @Query private var tasks: [Task]
    @Environment(\.modelContext) private var context
    
    var body: some View {
        List {
            ForEach(tasks) { task in
                TaskRow(task: task)
            }
            .onDelete(perform: deleteTasks)
        }
    }
    
    private func deleteTasks(offsets: IndexSet) {
        for index in offsets {
            context.delete(tasks[index])
        }
    }
}

Adding Data

struct AddTaskView: View {
    @Environment(\.modelContext) private var context
    @State private var title = ""
    
    var body: some View {
        NavigationView {
            Form {
                TextField("Task Title", text: $title)
                
                Button("Save") {
                    let task = Task(title: title)
                    context.insert(task)
                    try? context.save()
                }
            }
        }
    }
}

🔗 Relationships

One-to-Many

@Model
class Project {
    var name: String
    var tasks: [Task] = []
    
    init(name: String) {
        self.name = name
    }
}

@Model
class Task {
    var title: String
    var project: Project?
    
    init(title: String, project: Project? = nil) {
        self.title = title
        self.project = project
    }
}

Many-to-Many

@Model
class Tag {
    var name: String
    var tasks: [Task] = []
    
    init(name: String) {
        self.name = name
    }
}

@Model
class Task {
    var title: String
    var tags: [Tag] = []
    
    init(title: String) {
        self.title = title
    }
}

🔍 Advanced Querying

Filtered Queries

struct CompletedTasksView: View {
    @Query(filter: #Predicate<Task> { $0.isCompleted })
    private var completedTasks: [Task]
    
    var body: some View {
        List(completedTasks) { task in
            Text(task.title)
        }
    }
}

Sorted Queries

struct TaskListView: View {
    @Query(sort: \Task.createdAt, order: .reverse)
    private var tasks: [Task]
    
    var body: some View {
        List(tasks) { task in
            TaskRow(task: task)
        }
    }
}

Dynamic Queries

struct FilteredTasksView: View {
    let searchText: String
    
    var body: some View {
        FilteredTasksList(searchText: searchText)
    }
}

struct FilteredTasksList: View {
    @Query private var tasks: [Task]
    
    init(searchText: String) {
        let predicate = #Predicate<Task> { task in
            searchText.isEmpty || task.title.localizedStandardContains(searchText)
        }
        _tasks = Query(filter: predicate, sort: \Task.createdAt)
    }
    
    var body: some View {
        List(tasks) { task in
            Text(task.title)
        }
    }
}

☁️ CloudKit Integration

Enable CloudKit Sync

@main
struct TaskApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Task.self) { result in
            switch result {
            case .success(let container):
                // Enable CloudKit sync
                container.mainContext.cloudKitContainer = CKContainer.default()
            case .failure(let error):
                print("Failed to create container: \(error)")
            }
        }
    }
}

CloudKit Configuration

// In your model
@Model
class Task {
    @Attribute(.unique) var id: UUID
    var title: String
    var isCompleted: Bool
    
    init(title: String) {
        self.id = UUID()
        self.title = title
        self.isCompleted = false
    }
}

🎯 Best Practices

Model Design

@Model
class Task {
    // Use @Attribute for special configurations
    @Attribute(.unique) var id: UUID
    @Attribute(.spotlight) var title: String
    
    // Use relationships for complex data
    @Relationship(deleteRule: .cascade) var subtasks: [Subtask] = []
    
    // Computed properties for derived data
    var isOverdue: Bool {
        guard let dueDate = dueDate else { return false }
        return dueDate < Date() && !isCompleted
    }
    
    init(title: String) {
        self.id = UUID()
        self.title = title
    }
}

Performance Optimization

// Use batch operations for large datasets
extension ModelContext {
    func batchDelete<T: PersistentModel>(_ type: T.Type, predicate: Predicate<T>) throws {
        let descriptor = FetchDescriptor<T>(predicate: predicate)
        let objects = try fetch(descriptor)
        
        for object in objects {
            delete(object)
        }
        
        try save()
    }
}

Error Handling

class DataManager: ObservableObject {
    let container: ModelContainer
    
    init() {
        do {
            container = try ModelContainer(for: Task.self)
        } catch {
            fatalError("Failed to create ModelContainer: \(error)")
        }
    }
    
    func saveContext() {
        do {
            try container.mainContext.save()
        } catch {
            print("Failed to save context: \(error)")
        }
    }
}

📊 Migration from Core Data

Model Conversion

// Core Data (old)
@NSManaged public var title: String?
@NSManaged public var isCompleted: Bool

// SwiftData (new)
var title: String
var isCompleted: Bool

Context Usage

// Core Data (old)
let context = persistentContainer.viewContext
let task = Task(context: context)

// SwiftData (new)
@Environment(\.modelContext) private var context
let task = Task(title: "New Task")
context.insert(task)

🔧 Testing SwiftData

Unit Testing

import Testing
import SwiftData

@Test func testTaskCreation() throws {
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try ModelContainer(for: Task.self, configurations: config)
    let context = container.mainContext
    
    let task = Task(title: "Test Task")
    context.insert(task)
    
    #expect(task.title == "Test Task")
    #expect(task.isCompleted == false)
}

SwiftData provides a modern, Swift-native approach to data persistence with seamless SwiftUI integration.