WWDC Quick Look 💓 By SwiftGGTeam
Build an app with SwiftData

Build an app with SwiftData

观看原视频

Highlight

SwiftData 通过 @Model 宏、@Query 属性包装器和 .modelContainer() 修饰符,让 SwiftUI 应用的数据持久化只需要几行代码,同时自动获得 Observable 能力、自动保存和文档型应用支持。

核心内容

从内存到持久化:一步之遥

以前,给 SwiftUI 应用添加数据持久化需要引入 Core Data,学习复杂的对象图管理、上下文保存和迁移规则。很多开发者因此放弃持久化,数据只在内存中存在,应用关闭就丢失。

SwiftData 改变了这个局面。它把数据持久化变得和声明 UI 一样简单。

03:33)给现有模型类加上 @Model 宏,它就变成可持久化的 SwiftData 模型。同时自动获得 Observable 协议的一致性,不需要 @Published,不需要 ObservableObject

@Query 自动查询和刷新

05:52)用 @Query 替换 @State,视图就能直接查询 SwiftData 存储中的模型。数据变化时视图自动刷新,和 @State 的行为一致。

ModelContainer 配置存储栈

08:27)通过 .modelContainer() 修饰符在视图层级中配置存储栈。WindowGroup 级别的 container 被该组创建的所有窗口共享。也可以给单个视图配置独立的 container。

自动保存

11:13)SwiftData 在 UI 事件和用户输入时自动保存 model context,不需要手动调用 save()。只有在需要立即持久化的场景(如分享数据前)才需要显式调用。

文档型应用

13:34)SwiftUI 支持 SwiftData 驱动的文档型应用。用 DocumentGroup 初始化器指定模型类型和内容类型,SwiftData 自动处理每个文档的存储。用户获得标准的文档操作:新建、打开、保存、分享。

详细内容

定义 SwiftData 模型

03:33)闪卡应用的 Card 模型改造:

import SwiftData

@Model
final class Card {
    var front: String
    var back: String
    var creationDate: Date

    init(front: String, back: String, creationDate: Date = .now) {
        self.front = front
        self.back = back
        self.creationDate = creationDate
    }
}

关键点:

  • import SwiftData 引入框架
  • @Model 宏让类可持久化
  • 不需要 ObservableObject,不需要 @Published
  • @Model 自动添加 Observable 协议一致性
  • 存储属性自动成为可观察属性

绑定到 SwiftData 模型

04:25)编辑视图用 @Bindable 创建绑定:

struct CardEditorView: View {
    @Bindable var card: Card

    var body: some View {
        Form {
            TextField("Front", text: $card.front)
            TextField("Back", text: $card.back)
        }
    }
}

关键点:

  • @Bindable 是最轻量的绑定包装器
  • $card.front 生成 Binding<String>
  • TextField 读取和写入绑定值
  • 不需要 @ObservedObject

查询模型

05:52)内容视图用 @Query 获取数据:

struct ContentView: View {
    @Query private var cards: [Card]

    var body: some View {
        List(cards) { card in
            CardRow(card: card)
        }
    }
}

关键点:

  • @Query 自动从 SwiftData 存储查询数据
  • 数据变化时视图自动刷新
  • 支持排序、过滤和动画配置
  • 使用视图继承的 model context 作为数据源

带排序的查询:

@Query(sort: \Card.creationDate, order: .reverse) private var cards: [Card]

关键点:

  • sort 参数指定排序键路径
  • order: .reverse 按降序排列
  • 也支持多字段排序和过滤条件

设置 ModelContainer

08:27)在 App 定义中配置:

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

关键点:

  • .modelContainer(for:) 为 WindowGroup 设置存储栈
  • 该组创建的所有窗口共享同一个 container
  • 子视图只能操作 container 中声明的模型类型
  • 没有配置 container 的视图无法使用 SwiftData

为 Preview 提供样本数据

09:24)创建内存中的 container 填充预览数据:

import SwiftData

@MainActor
let previewContainer: ModelContainer = {
    let schema = Schema([Card.self])
    let config = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)
    let container = try! ModelContainer(for: schema, configurations: config)

    let sampleCards = [
        Card(front: "Who invented the compiler?", back: "Grace Hopper"),
        Card(front: "What is SwiftData?", back: "A persistence framework")
    ]

    for card in sampleCards {
        container.mainContext.insert(card)
    }

    return container
}()

#Preview {
    ContentView()
        .frame(minWidth: 500, minHeight: 500)
        .modelContainer(previewContainer)
}

关键点:

  • isStoredInMemoryOnly: true 创建内存数据库
  • 在 Preview 中用 .modelContainer(previewContainer) 注入
  • 预览中显示真实数据,方便调试 UI
  • @MainActor 确保在主线程创建

创建和保存模型

10:30)通过 model context 插入新数据:

struct CardListView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var cards: [Card]

    var body: some View {
        List(cards) { card in
            Text(card.front)
        }
        .toolbar {
            Button("Add", systemImage: "plus") {
                let newCard = Card(front: "Sample Front", back: "Sample Back")
                modelContext.insert(newCard)
            }
        }
    }
}

关键点:

  • @Environment(\.modelContext) 获取视图的 model context
  • modelContext.insert() 把新模型加入上下文
  • SwiftData 自动保存,不需要手动调用 save()
  • 自动保存由 UI 事件和用户输入触发
  • 只有在需要立即持久化时才显式调用 save()

文档型应用

13:34)用 SwiftData 做存储的文档应用:

@main
struct SwiftDataFlashCardSample: App {
    var body: some Scene {
        #if os(iOS) || os(macOS)
        DocumentGroup(editing: Card.self, contentType: .flashCards) {
            ContentView()
        }
        #else
        WindowGroup {
            ContentView()
                .modelContainer(for: Card.self)
        }
        #endif
    }
}

关键点:

  • DocumentGroup(editing:contentType:) 指定模型类型和内容类型
  • SwiftData 自动为每个文档创建独立的 model container
  • 不需要手动设置 .modelContainer()
  • 获得标准的文档操作:新建、打开、保存、分享

定义内容类型:

import UniformTypeIdentifiers

extension UTType {
    static var flashCards: UTType {
        UTType(exportedAs: "com.example.flashcards")
    }
}

在 Info.plist 中声明:

<key>UTExportedTypeDeclarations</key>
<array>
    <dict>
        <key>UTTypeIdentifier</key>
        <string>com.example.flashcards</string>
        <key>UTTypeDescription</key>
        <string>Flash Cards Deck</string>
        <key>UTTypeConformsTo</key>
        <array>
            <string>com.apple.package</string>
        </array>
        <key>UTTypeTagSpecification</key>
        <dict>
            <key>public.filename-extension</key>
            <string>sampledeck</string>
        </dict>
    </dict>
</array>

关键点:

  • 内容类型标识符要和代码中的一致
  • SwiftData 文档是 package 类型
  • 指定文件扩展名帮助系统识别
  • com.apple.package 表示目录结构的文档

核心启发

  1. 把现有 SwiftUI 应用加上数据持久化

    • 给模型类加 @Model 宏,替换 @State@Query
    • 在 App 定义中加 .modelContainer(for:)
    • 不需要重写数据层,改动量极小
    • 入口:@Model + @Query + .modelContainer()
  2. 做一个跨平台的笔记应用

    • 用 SwiftData 存储笔记,支持 iOS、macOS、watchOS、tvOS
    • @Query 的排序和过滤功能实现文件夹和标签
    • 入口:@Query(sort: \Note.creationDate) + \Note.folder
  3. 构建文档型创作工具

    • DocumentGroup 让每个文档是独立的 SwiftData 存储
    • 用户可以在不同窗口打开不同文档
    • 文档可以直接分享和同步
    • 入口:DocumentGroup(editing: Model.self, contentType: .customType)
  4. 为 Preview 创建可复用的样本数据容器

    • 创建 previewContainer 单例,填充各种测试数据
    • 所有 Preview 共享同一个内存数据库
    • 入口:ModelConfiguration(isStoredInMemoryOnly: true)

关联 Session

评论

GitHub Issues · utterances