大多数浏览器和
Developer App 均支持流媒体播放。
-
使用 SwiftData 历史记录 API 跟踪模型更改
利用 SwiftData,直观呈现你的模型变更历史记录!使用历史记录 API 来了解数据存储何时发生了变更,并学习如何根据这些信息为你的 App 构建远程服务器同步和进程外变更处理等功能。我们还将介绍如何将对历史记录 API 的支持构建到自定数据存储中。
章节
- 0:00 - Introduction
- 0:45 - Fundamentals
- 5:18 - Transactions and changes
- 12:37 - Custom stores
资源
相关视频
WWDC24
-
下载Array
-
-
4:57 - Preserve values in history on deletion
// Add .preserveValueOnDeletion to capture unique columns import SwiftData @Model class Trip { #Unique<Trip>([\.name, \.startDate, \.endDate]) @Attribute(.preserveValueOnDeletion) var name: String var destination: String @Attribute(.preserveValueOnDeletion) var startDate: Date @Attribute(.preserveValueOnDeletion) var endDate: Date var bucketList: [BucketListItem] = [BucketListItem]() var livingAccommodation: LivingAccommodation? }
-
6:26 - Fetch transactions from history
private func findTransactions(after token: DefaultHistoryToken?, author: String) -> [DefaultHistoryTransaction] { var historyDescriptor = HistoryDescriptor<DefaultHistoryTransaction>() if let token { historyDescriptor.predicate = #Predicate { transaction in (transaction.token > token) && (transaction.author == author) } } var transactions: [DefaultHistoryTransaction] = [] let taskContext = ModelContext(modelContainer) do { transactions = try taskContext.fetchHistory(historyDescriptor) } catch let error { print(error) } return transactions }
-
7:34 - Process history changes
private func findTrips(in transactions: [DefaultHistoryTransaction]) -> (Set<Trip>, DefaultHistoryToken?) { let taskContext = ModelContext(modelContainer) var resultTrips: Set<Trip> = [] for transaction in transactions { for change in transaction.changes { let modelID = change.changedPersistentIdentifier let fetchDescriptor = FetchDescriptor<Trip>(predicate: #Predicate { trip in trip.livingAccommodation?.persistentModelID == modelID }) let fetchResults = try? taskContext.fetch(fetchDescriptor) guard let matchedTrip = fetchResults?.first else { continue } switch change { case .insert(_ as DefaultHistoryInsert<LivingAccommodation>): resultTrips.insert(matchedTrip) case .update(_ as DefaultHistoryUpdate<LivingAccommodation>): resultTrips.update(with: matchedTrip) case .delete(_ as DefaultHistoryDelete<LivingAccommodation>): resultTrips.remove(matchedTrip) default: break } } } return (resultTrips, transactions.last?.token) }
-
10:19 - Save and use a history token
private func findUnreadTrips() -> Set<Trip> { let tokenData = UserDefaults.standard.data(forKey: UserDefaultsKey.historyToken) var historyToken: DefaultHistoryToken? = nil if let tokenData { historyToken = try? JSONDecoder().decode(DefaultHistoryToken.self, from: tokenData) } let transactions = findTransactions(after: historyToken, author: TransactionAuthor.widget) let (unreadTrips, newToken) = findTrips(in: transactions) if let newToken { let newTokenData = try? JSONEncoder().encode(newToken) UserDefaults.standard.set(newTokenData, forKey: UserDefaultsKey.historyToken) } return unreadTrips }
-
11:30 - Update the user interface
struct ContentView: View { @Environment(\.scenePhase) private var scenePhase @State private var showAddTrip = false @State private var selection: Trip? @State private var searchText: String = "" @State private var tripCount = 0 @State private var unreadTripIdentifiers: [PersistentIdentifier] = [] var body: some View { NavigationSplitView { TripListView(selection: $selection, tripCount: $tripCount, unreadTripIdentifiers: $unreadTripIdentifiers, searchText: searchText) .toolbar { ToolbarItem(placement: .topBarLeading) { EditButton() .disabled(tripCount == 0) } ToolbarItemGroup(placement: .topBarTrailing) { Spacer() Button { showAddTrip = true } label: { Label("Add trip", systemImage: "plus") } } } } detail: { if let selection = selection { NavigationStack { TripDetailView(trip: selection) } } } .task { unreadTripIdentifiers = await DataModel.shared.unreadTripIdentifiersInUserDefaults } .searchable(text: $searchText, placement: .sidebar) .sheet(isPresented: $showAddTrip) { NavigationStack { AddTripView() } .presentationDetents([.medium, .large]) } .onChange(of: selection) { _, newValue in if let newSelection = newValue { if let index = unreadTripIdentifiers.firstIndex(where: { $0 == newSelection.persistentModelID }) { unreadTripIdentifiers.remove(at: index) } } } .onChange(of: scenePhase) { _, newValue in Task { if newValue == .active { unreadTripIdentifiers += await DataModel.shared.findUnreadTripIdentifiers() } else { // Persist the unread trip names for the next launch session. await DataModel.shared.setUnreadTripIdentifiersInUserDefaults(unreadTripIdentifiers) } } } #if os(macOS) .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in Task { unreadTripIdentifiers += await DataModel.shared.findUnreadTripIdentifiers() } } .onReceive(NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification)) { _ in Task { await DataModel.shared.setUnreadTripIdentifiersInUserDefaults(unreadTripIdentifiers) } } #endif } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。