iOS SwiftData 本地資料庫教學

SwiftData 是 Apple 在 2023 年推出的全新資料庫框架,旨在取代 Core Data。它完全為了 SwiftUI 設計,結合了 Macro 語法,使用起來就像操作一般的 Swift Class 一樣簡單。

定義模型 (@Model)

只需要在 Class 前面加上 @Model,它就變成一個資料庫表格 (Table)。

import SwiftData

@Model
class TodoItem {
    // 1. 使用 @Attribute(.unique) 確保標題不重複
    @Attribute(.unique) var title: String
    var createdAt: Date
    var isCompleted: Bool
    
    // 2. 關聯 (Relationship)
    // 假設我們有另一個 Category 模型,這裡定義「多對一」關係
    var category: Category?
    
    init(title: String, category: Category? = nil) {
        self.title = title
        self.createdAt = Date()
        self.isCompleted = false
        self.category = category
    }
}

@Model
class Category {
    @Attribute(.unique) var name: String
    
    // 定義「一對多」關係,並設定刪除規則 (當分類被刪除時,其下的 Item 也一併刪除)
    @Relationship(deleteRule: .cascade, inverse: \TodoItem.category)
    var items: [TodoItem] = []
    
    init(name: String) {
        self.name = name
    }
}

設置容器 (.modelContainer)

在 App 的進入點注入 ModelContainer。

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // 啟用 SwiftData,並註冊所有需要用到的 Model
        .modelContainer(for: [TodoItem.self, Category.self]) 
    }
}

Preview 預覽設定

若是為了 Xcode Preview,我們可以建立一個記憶體容器 (In-Memory Container):

@MainActor
let previewContainer: ModelContainer = {
    let config = ModelConfiguration(isStoredInMemoryOnly: true)
    let container = try! ModelContainer(for: TodoItem.self, configurations: config)
    // 塞入假資料...
    return container
}()

#Preview {
    ContentView()
        .modelContainer(previewContainer)
}

操作資料 (CRUD)

讀取與過濾 (@Query)

使用 @Query 並搭配 #Predicate macro 來進行高效過濾。

struct ContentView: View {
    @Environment(\.modelContext) private var context
    
    // 1. 基本查詢 (依時間排序)
    @Query(sort: \TodoItem.createdAt, order: .reverse) private var items: [TodoItem]
    
    // 2. 過濾查詢 (只顯示未完成的項目)
    @Query(filter: #Predicate<TodoItem> { !$0.isCompleted }, sort: \.createdAt)
    private var activeItems: [TodoItem]
    
    var body: some View {
        List {
            ForEach(items) { item in
                HStack {
                    Text(item.title)
                    Spacer()
                    if item.isCompleted {
                        Image(systemName: "checkmark")
                    }
                }
                // 點擊切換完成狀態 (更新資料)
                .onTapGesture {
                    item.isCompleted.toggle() 
                    // SwiftData 會自動追蹤變更並存檔,無需手動 save()
                }
            }
            .onDelete(perform: deleteItems)
        }
    }
    
    func deleteItems(at offsets: IndexSet) {
        for index in offsets {
            let item = items[index]
            context.delete(item) // 刪除資料
        }
    }
}

新增 (Create)

func addItem() {
    let newItem = TodoItem(title: "寫程式")
    context.insert(newItem) // 插入資料
    // 系統會自動存檔,不用手動 save()
}

更新 (Update)

SwiftData 的物件是 Observable 的。只要你在查詢出來的物件上修改屬性,UI 會自動刷新,資料庫也會自動更新

// 直接修改屬性即可
item.title = "新標題"
item.isCompleted = true

這與 Core Data 有什麼不同?

SwiftData 其實底層仍然是 Core Data,但它隱藏了所有複雜的設定(如 .xcdatamodeld 檔案、NSManaedObjectContext 的手動儲存等)。

特性SwiftDataCore Data
定義模型純 Swift Code (@Model)圖形介面 (.xcdatamodeld)
資料儲存自動儲存 (Auto-save)需要手動 context.save()
查詢語法Swift-like (#Predicate)Objective-C 風格 (NSPredicate)
學習曲線

對於新的 iOS 17+ 專案,強烈建議直接使用 SwiftData。