Build an app with SwiftData
Swift & UI 进阶 20m

用 SwiftData 构建应用

Build an app with SwiftData

2023年6月5日

在 Apple 官方观看视频

一句话判断

这是一场手把手教学——用闪卡应用做实例,演示如何用 @Model、@Query、ModelContainer 三板斧在 SwiftUI 中集成 SwiftData,从零到完整持久化只需要改几行代码。

这场 Session 讲了什么

SwiftUI 工程师 Julia 以一个闪卡(flashcards)应用为实例,演示了 SwiftData 和 SwiftUI 的无缝集成。整个 Session 是 code-along 格式,提供了配套的 Xcode 起始项目和完成项目。

模型改造极简:只需给现有类加上 @Model 宏,它就自动获得 SwiftData 的持久化能力和 Observable 协议的一致性。不需要手写 Codable,不需要 @Published 属性包装器,不需要 ObservableObject。

@Query 查询:用 @Query 替换 @State,视图就能直接查询 SwiftData 存储中的模型。@Query 会自动在模型数据变化时触发视图更新。支持排序、过滤和动画配置。

ModelContainer 配置:通过 .modelContainer() 视图修饰符为视图层级提供数据源。整个 WindowGroup 共享一个容器,或不同视图使用不同容器。没有配置 ModelContainer 的视图无法使用 SwiftData。

自动保存:SwiftData 会自动在 UI 事件和用户输入时保存 ModelContext,不需要手动调用 modelContext.save()

Bindable 绑定:配合新的 @Bindable 属性包装器,可以直接将模型的可变属性绑定到 UI 控件(如 TextField),数据流代码比以前更少。

Session 还展示了如何用 SwiftData 创建基于文档的应用——SwiftData 自动处理文档存储,极大地简化了文档型应用的开发。

值得深挖的点

@Model 宏的复合能力:一个宏同时解决了持久化(自动实现 Codable 等协议)、可观察性(自动遵循 Observable)和变更追踪。对比 Core Data 需要的 NSManagedObject 子类、手动属性声明和 NSFetchedResultsController,代码量差距巨大。

@Query 的灵活性:每个视图可以有多个 @Query 属性,各自独立配置排序和过滤条件。@Query 底层使用视图的 ModelContext 作为数据源,不需要额外的数据库连接管理。

ModelContainer 的层级设计:可以在 WindowGroup 级别设置(所有窗口共享),也可以在单个视图级别设置(不同视图用不同存储栈)。这种灵活性意味着同一个应用可以有多个独立的存储空间——比如主数据用一个容器,设置数据用另一个。

Preview 的数据支持:Session 展示了如何创建内存中的 ModelContainer 来填充 Preview 数据。这对开发体验很重要——你不需要每次启动应用才能看到数据效果。

代码片段

// 1. 模型定义 - 只需加 @Model 宏
import SwiftData

@Model
class Card {
    var frontText: String
    var backText: String
    var creationDate: Date

    init(frontText: String, backText: String) {
        self.frontText = frontText
        self.backText = backText
        self.creationDate = Date()
    }
}
// 2. 查询和显示
struct ContentView: View {
    // 用 @Query 替换 @State,自动查询并响应数据变化
    @Query(sort: \Card.creationDate, order: .reverse)
    var cards: [Card]

    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 150))]) {
            ForEach(cards) { card in
                CardView(card: card)
            }
        }
    }
}
// 3. 应用入口配置 ModelContainer
@main
struct FlashCardsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // 为整个 WindowGroup 设置 ModelContainer
        .modelContainer(for: Card.self)
    }
}
// 4. 创建和保存模型
struct AddCardView: View {
    @Environment(\.modelContext) private var modelContext
    @Bindable var card: Card  // 用 Bindable 直接绑定

    var body: some View {
        Form {
            TextField("正面", text: $card.frontText)
            TextField("背面", text: $card.backText)
        }
    }

    func saveCard() {
        let newCard = Card(frontText: "问题", backText: "答案")
        modelContext.insert(newCard)
        // 不需要手动 save() - SwiftData 自动保存
    }
}
// 5. Preview 用的内存容器
#Preview {
    ContentView()
        .modelContainer(
            for: Card.self,
            inMemory: true  // 内存模式,不写入磁盘
        )
}

最佳实践

  • 先看 “Meet SwiftData” 再看这场 Session:这场是集成实战,基础概念在前一场中讲解。
  • 用 @Bindable 替代 @ObservedObject:配合 SwiftData 的 @Model 和新的 Observable 协议,数据绑定代码更少。
  • 为 Preview 准备内存容器:创建 inMemory 的 ModelContainer 填充样本数据,让 Preview 能展示真实数据。
  • 不要手动调用 save():SwiftData 会在合适的时机自动保存。手动调用通常是不必要的。
  • ModelContainer 中只列出需要的模型类型:子视图只能操作 ModelContainer 中声明的模型类型,合理规划容器的作用范围。

还有什么值得关注

  • SwiftData 支持所有 Apple 平台——Mac、iPhone、Watch、TV,一个持久化方案覆盖全部。
  • Session 提到了基于文档的应用(document-based app)可以极其简单地用 SwiftData 实现,这个方向值得深入探索。
  • @Query 支持动画配置——数据变化时的列表更新可以自动带动画,不需要手动管理。
  • SwiftData 与新的 Observation 框架深度整合,建议配合 “Discover Observation with SwiftUI” Session 一起理解。
  • Session 提到应用可以创建多个 ModelContainer——这意味着复杂应用可以为不同功能模块设置独立的存储栈。
WWDC 2023