View in English

  • Apple 开发者
    • 入门汇总

    探索“入门汇总”

    • 概览
    • 学习
    • Apple Developer Program

    及时了解最新动态

    • 最新动态
    • 开发者你好
    • 平台

    探索“平台”

    • Apple 平台
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    • App Store

    精选

    • 设计
    • 分发
    • 游戏
    • 配件
    • 网页
    • Home
    • CarPlay 车载
    • 技术

    探索“技术”

    • 概览
    • Xcode
    • Swift
    • SwiftUI

    精选

    • 辅助功能
    • App Intents
    • Apple 智能
    • 游戏
    • 机器学习与 AI
    • 安全性
    • Xcode Cloud
    • 社区

    探索“社区”

    • 概览
    • “与 Apple 会面交流”活动
    • 社区主导的活动
    • 开发者论坛
    • 开源

    精选

    • WWDC
    • Swift Student Challenge
    • 开发者故事
    • App Store 大奖
    • Apple 设计大奖
    • Apple Developer Centers
    • 文档

    探索“文档”

    • 文档库
    • 技术概述
    • 示例代码
    • 《人机界面指南》
    • 视频

    发布说明

    • 精选更新
    • iOS
    • iPadOS
    • macOS
    • watchOS
    • visionOS
    • Apple tvOS
    • Xcode
    • 下载

    探索“下载”

    • 所有下载
    • 操作系统
    • 应用程序
    • 设计资源

    精选

    • Xcode
    • TestFlight
    • 字体
    • SF Symbols
    • Icon Composer
    • 支持

    探索“支持”

    • 概览
    • 帮助指南
    • 开发者论坛
    • “反馈助理”
    • 联系我们

    精选

    • 《开发者账户帮助》
    • 《App 审核指南》
    • 《App Store Connect 帮助》
    • 即将实行的要求
    • 协议和准则
    • 系统状态
  • 快速链接

    • 活动
    • 新闻
    • 论坛
    • 示例代码
    • 视频
 

视频

打开菜单 关闭菜单
  • 专题
  • 所有视频
  • 关于

更多视频

  • 简介
  • 概要
  • 转写文稿
  • 代码
  • 跟随编程:使用 SwiftData 添加持久化功能

    跟着我们为现有 App 添加持久化功能的演示,体验 SwiftData 的实际应用。我们将介绍如何定义数据模型,并将持久数据无缝整合到 SwiftUI 中。你还将了解一些基础技能,以便利用这个富有表现力的声明式 API 来管理 App 状态。

    章节

    • 0:00 - Introduction
    • 1:05 - Identify relevant state
    • 3:17 - Define your schemas
    • 9:41 - Define model relationships
    • 13:33 - Update the view layer
    • 21:47 - Next steps

    资源

    • Wishlist: Planning travel in a SwiftUI app
    • SwiftData
      • 高清视频
      • 标清视频

    相关视频

    WWDC26

    • SwiftData 的新功能

    WWDC25

    • SwiftData:深入了解继承和架构迁移
  • 搜索此视频…

    你好 我是Matthew Turk SwiftData团队的工程师 今天 我想向你展示 如何获取现有SwiftUI App的动态数据 并将其连接到适用于 Apple所有平台的现代持久化层 借助SwiftData的强大功能 我们将从一个名为 Wishlist的列表式App的源代码开始 Wishlist帮助我记录想法 在手机上整理旅行计划 并将旅行按 季节分组整理 欢迎从developer.apple.com 下载示例App跟着操作! 在本视频中 我们将逐步 查看项目文件以确定数据类型 以及用于SwiftData 模型和架构的变量 这些模型在数据库中 应如何相互关联 以及如何更新SwiftUI视图 以呈现新模型 并兼顾性能 互操作性和可扩展性 以适应更复杂的用例

    让我们预览一下 数据流的实际效果

    这是Wishlist标签页 顶部显示最近的旅行 向下滚动可见 多个主题旅行列表

    在Goals标签页 我可以看到各种目标徽章 这些是我希望在 旅行中完成的目标 在Search标签页 我可以筛选旅行和活动 我们来搜索沿海小径 排名第一

    这次旅行还剩 五项活动 我曾驾车前往Point Reyes 并在那里看到了11头麋鹿 所以我将勾选这一项

    返回Wishlist标签页 点击加号按钮添加新旅行 我想有一天再次看到极光 我将旅行命名为 "Northern Lights"并选择照片

    按下Done 这就是我的新旅行

    你刚才看到的数据流之所以成立 是因为Wishlist中的视图 通过SwiftUI环境 引入了DataSource变量 DataSource类管理并提供 所有预装的旅行数据 所有旅行、目标和搜索结果 都按需在内存中过滤和排序 对于小型示例 这没有问题 但在实践中 需要进行优化 以保持App面向用户 的部分轻量高效 此外 Wishlist依赖内存(RAM) 来处理和存储这些数据 而内存并不是稳定的存储场所 无法保存新旅行或活动供日后使用

    如果我关闭App 并重新运行

    我新建的旅行 会全部消失 重置为预装内容

    但事情不必如此 这正是SwiftData 所解决的那类问题 到目前为止 我们已确定相关状态 即旅行合集、目标状态 和搜索结果 SwiftData可以连接该状态 通过模型上下文 连接到持久存储介质 这就是本视频结束时 我们将实现的目标 我们下一步是找到这些 数据结构在代码中的位置 然后使用SwiftData架构 将其重构为模型 这样我们就可以 替换内存中的DataSource 改用持久化的ModelContext 从而能够编写高效的 数据库查询来驱动视图 让我们仔细看看其中一个模型 ——Activity类型

    要持久化Activities 第一步是导入SwiftData 并将Observable宏 替换为Model宏 SwiftData会自动为我们 生成可观察性conformance 太棒了! 顺便一提 name和isComplete上的didSet观察者 会在任一属性更改时 设置活动的dateEdited值

    假设我有一个滑翔伞活动 我尚未完成它 上次编辑 时间是4月1日上午9:41 以下是事件时间线 假设我决定将活动 从滑翔伞改为游泳 name的属性观察者 触发并更新dateEdited 之后 我去海滩旅行 并勾选了该活动 isComplete的属性观察者 触发并更新dateEdited 自动更新的dateEdited属性 是一种很好的方式 可用于列表的排序或筛选 然而 属性观察者和计算属性 并不总是兼容 因此我将使用其他技术 保持dateEdited的最新状态 但在此之前 我会先让 当前项目重新成功构建 我稍后会回到这张图 现在 只需移除 这些didSet块

    并保持dateEdited不变 好的 接下来是Trip类 同样 导入SwiftData

    并替换宏

    现在我们来检查这些构建失败 以了解缺少什么 这个错误提示 creationDate必须是可变的 因此我们将声明 调整为使用var而非let

    现在SwiftData可以 在运行时填充此属性 使用从数据库加载的值 下一个错误提示 TripCollection属性 以及所有模型属性 必须符合Codable 此要求的存在 是为了让SwiftData能将 属性序列化为数据库列 旅行合集存储 旅行的季节主题 它应该包含在架构中 并被持久化 我将按住Command点击 跳转到其声明处 并显式添加Codable conformance 仍有几个构建失败 我们将在继续构建架构时 修复这些问题 接下来 我们来看看 Wishlist中目标跟踪的工作原理 对于这个Goal类型 变更并不像转换那样简单 将Observable转换为Model Goal被声明为枚举 每个目标都有属性 如名称 类型——例如跟踪活动 已完成还是旅行已完成—— 以及实现目标所需的 旅行或活动目标数量 共有18个Goals 全部提前定义好 因为枚举定义了 一个封闭的值集合 这对于最初的界面演示来说没问题 但持久化模型 需要的是类 类可以存储属性 并且可以实例化任意次数 为了使界面正确 我们需要重新思考Goal的设计 使其与Goals的处理 和呈现逻辑相协调 总体而言 这些目标中的每一个 都对应一个徽章 该徽章将根据是否满足 特定条件显示在Wishlist中 为了忠实地持久化 这些目标的状态 我们需要某种方式来捕获 并存储最简表示 以表明特定目标的 条件是否已满足 在SwiftData中 类是我们选择用来 表达这种最简表示的基础 的基础 我将把Goal转换为类 从相同的三个属性开始 name、kind和target count

    在原始Wishlist App中 已完成项目数量单独存储 因为枚举没有存储属性 现在我们有了持久化类 我们也在目标本身中 存储进度值 称为completedCount

    以及一个布尔属性isComplete 当completedCount 大于或等于target时 我们将存储true值 由于它是存储属性 在视图层开始查询时 可以方便地区分 已完成目标和待完成目标

    接下来我们需要 处理Kind属性 Wishlist用它来根据目标 是否与完成旅行 或完成活动相关 显示不同的详情 我们可以将这些细微差别 分解为新的子类 使用SwiftData对 模型继承的支持 继承是一种软件设计模式 在以下情况下往往有所回报 你有一个定义清晰的类层次结构 其中每个子类代表与父类相同的概念 与父类相同 但对一组公共属性 有更具体的表现形式 例如 螺旋星系 和透镜星系 是星系的子类 因为它们继承了任何类型星系 都具有的几个特征 比如恒星和尘埃 同时在可见形状上 也有本质差异 类似地 你可以有 旅行目标和活动目标 它们从goal父类 继承一些公共属性 我将通过继承 对不同类型的目标建模 方法是从Goal中移除Kind属性 并引入TripGoals 和ActivityGoals子类

    如需了解更多关于何时适合使用 模型继承的内容 请参阅 WWDC 2025 的"SwiftData: Dive into inheritance and schema migration"专场 我们现在拥有了 开始定义所有关系所需的一切 这些模型之间的关系 在Wishlist中 每次旅行都与一组活动相关联 这是一对多关系 每个持久化的Trip模型 可能对应许多持久化的Activity模型 Wishlist的多个部分一直在 通过使用字典 和遍历数组的函数 来近似实现一对多关系 例如 有一个基于活动ID 将Trips映射到Activities的字典 稍后 我将把这些字典 转换为类型之间 适当的一对多关系 其中每个Trip 可以有零个或多个Activities 声明数组是告知SwiftData 一种模型可以按需 从模型上下文引用 另一种模型的惯用方式 在Trip中 我将声明 一个activities数组 这里我还添加了relationship宏 以明确标记Trip的activities 数组为关系 这样从数据库 中删除一次旅行时 也会清除其行程中 包含的activity模型 最后 这个photoURL属性 需要调整 它现在只是一个文件路径 如果文件被重命名 或移动到其他目录 它将失去所有意义 并且 完整分辨率图片 应该只在视图 需要完整显示时才加载 例如在TripDetailView中 在滚动浏览旅行轮播时 我们只显示缩略图 因此在这里添加一个 名为thumbnailData的新属性 这将缓存所选照片的 低分辨率版本 并将其原始字节内联 存储在数据库中 然后另外以持久化的 外部文件引用方式 存储完整分辨率图片 而非使用URL 在SwiftData中 这通过 为图片创建一个新模型来实现 我已经将其作为 TripImage类型添加好了 我们不会在这里介绍 这些实现细节 但我鼓励你稍后 阅读示例代码了解更多 由于photoURL不再是URL 我们来将其重命名为photo 右键点击声明处 并在重构菜单中 选择Rename 我使用多光标编辑 因此多个文件将同时更新 同时 我可以点击这条注释 用新变量名更新它 按下Return 然后重构初始化器的标签

    并调整函数体 以直接设置activities

    通过建立这个关系 我们复现了之前的能力 使活动驱动的视图能够 引用并显示其父旅行的名称、季节 以及其他详情 现在我们真正开始 集成这些SwiftData功能了 项目中有一些多余文件 不再需要TripEditModel了 因为SwiftUI视图可以直接绑定到 SwiftData模型 并实时传播编辑 DataSource的所有职责 由ModelContext自动处理 查询和关系 删除

    值得思考最后那部分 我们刚从项目中 移除了数百行代码 大量的状态管理、存储逻辑、 筛选、排序 关系遍历和搜索 将开箱即用 最后在WindowGroup做一处调整 模型层就可以完成了 这里的modelContainer场景修饰器 告知SwiftUI 使用我们的新架构 与query宏配合 接下来 我们将更新视图层 有了架构和 模型容器就位 自动保存默认已启用 在实际演示之前 我们将集成这些持久化模型 从针对每个呈现模型的子视图 的高效定向查询开始 然后 我们将使用SwiftUI视图修饰器 来捕获并呈现运行时可能发生的错误 例如磁盘容量不足 或不支持的谓词 最后 我们将添加回 缺失的属性观察者 以确保UI事件 传播所有正确的数据 以及我们所期望的副作用 在为SwiftData App添加筛选功能时 有两个关键点需要牢记 在查询中 FetchDescriptor 是你规划要加载 并通过模型上下文或查询 显示哪些模型的方式 其次 你的模型将保存到 存在于App地址空间之外的存储介质 App地址空间之外 例如本地文件系统中 的数据库或远程服务器 虽然这种存储是持久化层的基础 但其速度可能比从内存读取 慢几个数量级 因此在App中设计持久化层时 关键是要思考哪些数据 需要在何时位于何处 才能带来最佳体验 我来解释一下 以前 有关旅行、活动 和目标的数据 会保存在全局变量中 例如allGoals 它们作为App编译二进制文件 的一部分被加载 以这种方式访问数据速度很快 但如果我在allGoals中 硬编码大量元素 App的内存占用将 明显偏高 贯穿整个生命周期 而且如果我添加一个新目标 关于它的所有信息都会消失 当我关闭App 重新启动后 正如你所见 使用SwiftData 我们可以 插入或更新现有目标 并通过模型上下文保存 一旦保存好 我们有 几种方式可以将它们拉回App

    这是其中一种方式 这段代码获取所有Goals 然后丢弃不相关的 因此它使用的持续内存更少 但产生更多I/O 这种方式有点像让图书管理员 去图书馆每个书架 取来所有书籍 这样你就可以亲自 找出你最喜欢的作者的书 你本可以让那位图书管理员 在浏览书架时 直接找那位作者的书 使用谓词进行获取 就像提前告诉图书管理员 你想要什么 这里 我只获取我所需要的目标

    在GoalsView中 我们将使用带有谓词的query宏 这等同于在模型上下文上 调用fetch 其优势在于当查询结果 发生变化时 SwiftUI视图会自动更新 我们现在来做这件事 导入SwiftData

    并替换此dataSource 环境属性 改用查询获取已达成的目标 按达成时间排序 第二个查询获取 其余的相关目标 RecentTripsPageView中思路相同

    导入SwiftData

    并将dataSource替换为查询

    我们将按倒序时间顺序 获取旅行 并设置获取数量限制为5 这样我们就能获取最近的5次旅行 它们将直接进入这个ForEach 在TripCollectionView中 我们想获取所有旅行并按季节分组 每个季节就是一个旅行合集 每个tripCollection将有 这个视图的一个实例 单个TripCollectionView在 初始化之前并不知道 将显示哪个季节 因此我们将声明查询

    然后在初始化器中 动态构建它

    这是匹配所需合集的 所有旅行的显式查询 注意查询接收一个谓词 在谓词内部 tripCollection参数直接 从初始化器中捕获 然后再访问数据库 查询结果将进入 这个ForEach

    接下来是SearchResultsListView App第三个标签页中的视图 其父视图显示在 右侧预览中 拥有搜索字段和文本 并将值传递给此视图的初始化器 因此再次将dataSource 替换为查询声明 然后初始化器中的参数 将指导这些查询的 谓词如何构建 如果搜索文本为空 则回退到获取最近的三次旅行 否则获取名称与搜索文本匹配的 所有旅行 按字母顺序排序 然后对活动搜索做同样处理 检查文本是否与某次旅行中 任何活动的名称匹配 并同样设置属性包装器

    最后 在List中使用查询到的值

    这个列表还有一个 overlay视图修饰器 当没有找到搜索结果时 在顶部显示ContentUnavailableView 我们将原来的dataSource条件 替换为直接检查 trips.isEmpty和activities.isEmpty

    这样就足以让App再次运行了 我将在Wishlist中添加一次旅行 然后重新运行

    这次 我的旅行还在

    "Northern Lights" 向SwiftData的过渡 即将完成 我们只剩下几个收尾工作 看看ActivityItemView中的 updateGoalAchievements方法 它在用户完成活动时 直接更新进度值 它可能抛出错误 我将在状态变量中 捕获这些错误 我还会将错误传递给 我的遥测系统 以便将来改进App 在合理的情况下 我会弹出警告提示用户 如何从错误中恢复 这样就处理了一个错误情况 UI中还有几处 状态可能会过时的地方 之前我们移除了设置dateEdited的 didSet观察者 现在我们需要 重新添加这个行为 以下是这个Bug 假设我想按dateEdited 对另一次旅行的活动排序 在排序下拉菜单中 我将选择"Date Edited" 然后如果我勾选这个名为 "Meditate under a tree"的活动 它应该移到列表顶部 因为我刚刚更新了 它的一个属性 但它就停在那里不动 在持久化层方面 这里没有抛出任何错误 但还是有些不对劲 现在我们已经实现了持久化 我们将重新启用dateEdited 属性的实时更新 使用Continuous Observation功能 该功能已在2027版本的 Observation框架中添加

    在ActivityItemView的初始化器中 使用新的withContinuousObservation 函数设置观察者 这个视图是用户 编辑活动的地方 因此这里是放置观察的好位置 每当有人更改Activity的 isComplete或name时 Observation框架将运行 这段代码将dateEdited设置 为当前时间 触发查询自动更新活动列表 这也是在Trip上 添加副作用的自然位置 每当活动状态切换 或活动被添加或移除时 我们更新整个旅行 的isComplete属性

    现在你知道如何将Apple的 声明式持久化框架集成 到你自己的App中了 首先考虑App状态的 适当表示方式 并声明构成你架构的Model类型 然后使用谓词编写定向查询 在内存使用 与磁盘存储之间取得平衡 并持续关注如何进一步 调整SwiftUI视图 以实现与SwiftData的 最佳互操作性 就此 我可以勾选 今天的最后一项活动

    并获得我的徽章

    感谢收听 一路平安

    • 3:39 - Convert Activity to a persistent model with @Model

      import Foundation
      import SwiftData
      
      // SwiftData automatically generates Observable conformance
      @Model
      class Activity {
          var name: String
          var isComplete: Bool = false
          var dateCreated = Date.now
          var dateEdited = Date.now
      }
    • 6:06 - Add Codable conformance to TripCollection

      enum TripCollection: String, CaseIterable, RawRepresentable, Codable {
          case springEscapes
          case summerVibes
          case fallGetaways
          case winterRetreats
      }
    • 10:32 - Set up model relationships between Trip, TripImage, and Activity

      import Foundation
      import SwiftData
      
      @Model
      class Trip {
          var name: String
          var collection: TripCollection
        
          var photo: TripImage
          var thumbnailData: Data?
        
          @Relationship(deleteRule: .cascade, inverse: \Activity.trip)
          var activities: [Activity] = []
        
          private(set) var creationDate = Date.now
          var subtitle: String?
          var isComplete: Bool = false
      }
    • 13:21 - Enable interoperability between your schema and SwiftUI views

      import SwiftUI
      import SwiftData
      
      @main
      struct WishlistApp: App {
          let container: ModelContainer = {
              do {
                  let modelContainer = try ModelContainer(for: Trip.self, Activity.self, TripImage.self, Goal.self, TripGoal.self, ActivityGoal.self)
                  try SampleData.seedIfNeeded(in: modelContainer.mainContext)
                  return modelContainer
              } catch {
                  fatalError("Could not create model container: \(error)")
              }
          }()
      
          var body: some Scene {
              WindowGroup {
                  ContentView()
                      .preferredColorScheme(.dark)
              }
              .modelContainer(container)
          }
      }
    • 16:27 - Fetch achieved and upcoming goals

      @Query(filter: #Predicate<Goal> { $0.isAchieved }, sort: \Goal.dateAchieved, order: .reverse)
      private var achievedGoals: [Goal]
      
      @Query(filter: #Predicate<Goal> { !$0.isAchieved }, sort: \Goal.sortOrder)
      private var upcomingGoals: [Goal]
    • 16:49 - Fetch recent trips

      import SwiftUI
      import SwiftData
      
      struct RecentTripsPageView: View {
          // Fetch most recent trips in reverse chronological order
          @Query(FetchDescriptor<Trip>(sortBy: [SortDescriptor(\Trip.creationDate, order: .reverse)], fetchLimit: 5))
          private var trips: [Trip]
      
          @Namespace private var namespace
      
          var body: some View {
              TabView {
                  ForEach(trips) { trip in
                      NavigationLink {
                          TripDetailView(trip: trip)
                              .navigationTransition(
                                  .zoom(sourceID: trip.id, in: namespace))
                      } label: {
                          TripImageView(trip: trip)
                              .overlay(alignment: .bottomLeading) {
                                  VStack(alignment: .leading) {
                                      Text("RECENTLY ADDED")
                                          .font(.subheadline)
                                          .fontWeight(.bold)
                                          .foregroundStyle(.limeGreen)
      
                                      Text(trip.name)
                                          .font(.title)
                                          .fontWidth(.expanded)
                                          .fontWeight(.medium)
                                          .foregroundStyle(.primary)
                                  }
                                  .padding(.horizontal)
                                  .padding(.bottom, 54)
                              }
                              .matchedTransitionSource(id: trip.id, in: namespace)
                      }
                      .buttonStyle(.plain)
                  }
              }
              .tabViewStyle(.page)
              .containerRelativeFrame([.horizontal, .vertical]) { length, axis in
                  if axis == .vertical {
                      return length / 1.3
                  } else {
                      return length
                  }
              }
          }
      }
    • 17:26 - Dynamically construct a query in the initializer of TripCollectionView

      init(tripCollection: TripCollection, cardSize: TripCard.Size, namespace: Namespace.ID) {
          _trips = Query(filter: #Predicate<Trip> { $0.collection == tripCollection }, sort: \Trip.name)
          self.tripCollection = tripCollection
          self.cardSize = cardSize
          self.namespace = namespace
      }
    • 18:13 - Search for trips and activities by name

      import SwiftUI
      import SwiftData
      
      private struct SearchResultsListView: View {
          @Query(sort: \Trip.name) private var trips: [Trip]
          @Query(sort: \Activity.name) private var activities: [Activity]
      
          var searchText: String
          var namespace: Namespace.ID
      
          init(searchText: String, namespace: Namespace.ID) {
              self.searchText = searchText
              self.namespace = namespace
      
              if searchText.isEmpty {
                  _trips = Query(FetchDescriptor(sortBy: [SortDescriptor(\Trip.creationDate, order: .reverse)], fetchLimit: 3))
                  _activities = Query(filter: #Predicate<Activity> { _ in false })
              } else {
                  // All trips whose name matches searchText, sorted lexicographically
                  let tripSearchPredicate = #Predicate<Trip> { $0.name.localizedStandardContains(searchText) }
                  _trips = Query(filter: tripSearchPredicate, sort: \Trip.name)
                  // All matching activities that belong to a trip
                  let activitySearchPredicate = #Predicate<Activity> { $0.trip != nil && $0.name.localizedStandardContains(searchText) }
                  _activities = Query(filter: activitySearchPredicate, sort: \Activity.name)
              }
          }
      
          var body: some View {
              List {
                  if !trips.isEmpty {
                      TripSearchSectionView(trips: trips, namespace: namespace, title: searchText.isEmpty ? "Recent Trips" : "Trips")
                  }
      
                  if !activities.isEmpty {
                      ActivitySearchSectionView(activities: activities)
                  }
              }
              .overlay {
                  if trips.isEmpty && activities.isEmpty {
                      ContentUnavailableView(
                          "No results for “\(searchText)”",
                          systemImage: "magnifyingglass",
                          description: Text("Check spelling or try a new search.")
                      )
                  }
              }
              .listStyle(.plain)
          }
      }
    • 19:42 - Capture and report errors from ActivityItemView

      var body: some View {
          HStack(alignment: .firstTextBaseline, spacing: 17) {
              Group {
                  if isEditing {
                      rowContentWhenEditing
                  } else {
                      rowContentWhenNotEditing
                  }
              }
              .transition(.opacity.animation(.snappy))
              .animation(.snappy, value: isEditing)
          }
          .onDisappear {
              do {
                  try updateGoalAchievements()
              } catch {
                  updateError = error
                  reportError(error)
              }
          }
          .alert(error: $updateError) {
              // Customize the presentation of the error
          }
      }
    • 21:04 - Update dateEdited and propagate side effects on property changes

      init(activity: Activity, isLast: Bool, isEditing: Bool) {
          activity.token = withContinuousObservation(options: .didSet) { event in
              _ = activity.name
              _ = activity.isComplete
      
              if event.matches(\Activity.name) {
                  activity.dateEdited = .now
              }
      
              if event.matches(\Activity.isComplete) {
                  activity.dateEdited = .now
                  activity.trip?.isComplete = activity.trip?.activities.isEmpty == false
                  && activity.trip?.activities.allSatisfy { $0.isComplete } == true
              }
          }
          self.activity = activity
          self.isLast = isLast
          self.isEditing = isEditing
      }
    • 0:00 - Introduction
    • An introduction to the Wishlist sample app and the three steps for adopting SwiftData: identifying relevant state, defining schemas, and defining model relationships.

    • 1:05 - Identify relevant state
    • Identify the data types and variables in Wishlist — trip collections, goal statuses, and the DataSource — that will become SwiftData models connected through a ModelContext.

    • 3:17 - Define your schemas
    • Convert Activity, Trip, and Goal into @Model types. Covers handling property observers with the @Model macro, refactoring the Goal enumeration into a class hierarchy using inheritance with TripGoal and ActivityGoal subclasses, and inlining thumbnail data.

    • 9:41 - Define model relationships
    • Declare to-many relationships between Trip and Activity using the @Relationship macro, remove the now-redundant DataSource and TripEditModel helpers, and attach the modelContainer scene modifier to complete the model layer.

    • 13:33 - Update the view layer
    • Replace environment DataSource properties with @Query macros and targeted FetchDescriptor predicates in each subview. Covers autosave, surfacing runtime errors with SwiftUI view modifiers, and re-enabling dateEdited property observers using the new withContinuousObservation API.

    • 21:47 - Next steps
    • Key takeaways: design a schema that fits your data model, balance memory and disk usage with targeted queries, and plan for interoperability and extensibility as your app evolves.

Developer Footer

  • 视频
  • WWDC26
  • 跟随编程:使用 SwiftData 添加持久化功能
  • 打开菜单 关闭菜单
    • iOS
    • iPadOS
    • macOS
    • Apple tvOS
    • visionOS
    • watchOS
    打开菜单 关闭菜单
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    打开菜单 关闭菜单
    • 辅助功能
    • 配件
    • Apple 智能
    • App 扩展
    • App Store
    • 音频与视频 (英文)
    • 增强现实
    • 设计
    • 分发
    • 教育
    • 字体 (英文)
    • 游戏
    • 健康与健身
    • App 内购买项目
    • 本地化
    • 地图与位置
    • 机器学习与 AI
    • 开源资源 (英文)
    • 安全性
    • Safari 浏览器与网页 (英文)
    打开菜单 关闭菜单
    • 完整文档 (英文)
    • 部分主题文档 (简体中文)
    • 教程
    • 下载
    • 论坛 (英文)
    • 视频
    打开菜单 关闭菜单
    • 支持文档
    • 联系我们
    • 错误报告
    • 系统状态 (英文)
    打开菜单 关闭菜单
    • Apple 开发者
    • App Store Connect
    • 证书、标识符和描述文件 (英文)
    • 反馈助理
    打开菜单 关闭菜单
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program (英文)
    • Mini Apps Partner Program
    • News Partner Program (英文)
    • Video Partner Program (英文)
    • 安全赏金计划 (英文)
    • Security Research Device Program (英文)
    打开菜单 关闭菜单
    • 与 Apple 会面交流
    • Apple Developer Center
    • App Store 大奖 (英文)
    • Apple 设计大奖
    • Apple Developer Academies (英文)
    • WWDC
    阅读最近新闻。
    获取 Apple Developer App。
    版权所有 © 2026 Apple Inc. 保留所有权利。
    使用条款 隐私政策 协议和准则