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:深入了解继承和架构迁移
  • 搜索此视频…
    • 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. 保留所有权利。
    使用条款 隐私政策 协议和准则