У меня есть класс модели представления, в сочетании с представлением Swiftui, которые извлекают фильтрованные записи из SwiftData с использованием моделитора. Я использовал Swiftui .task (id
Код: Выделить всё
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}
struct ItemView: Identifiable, Sendable {
var id: PersistentIdentifier
var timestamp: Date
init(_ model: Item) {
id = model.id
timestamp = model.timestamp
}
}
@ModelActor
actor ThreadsafeBackgroundActor: Sendable {
private var context: ModelContext { modelExecutor.modelContext }
func fetchData(_ predicate: Predicate? = nil) throws -> [ItemView] {
let descriptor = if let p = predicate {
FetchDescriptor(predicate: p)
} else {
FetchDescriptor()
}
let items = try context.fetch(descriptor)
return items.map(ItemView.init)
}
}
< /code>
У меня также есть модель представления, называемая актером: < /p>
import SwiftUI
import SwiftData
@Observable
class ItemListViewModel {
enum State {
case idle
case loading
case failed(Error)
case loaded([ItemView])
}
private(set) var state = State.idle
func fetchData(container: ModelContainer, predicate: Predicate) async throws -> [ItemView] {
let service = ThreadsafeBackgroundActor(modelContainer: container)
return try await service.fetchData(predicate)
}
@MainActor func load(container: ModelContainer, predicate: Predicate) async {
state = .loading
do {
// Artificial delay to visualize loading state
try await Task.sleep(for: .seconds(1))
let items = try await fetchData(container: container, predicate: predicate)
state = .loaded(items)
} catch is CancellationError {
state = .idle
} catch {
state = .failed(error)
}
}
}
Код: Выделить всё
import SwiftUI
import SwiftData
struct ContentView: View {
@State private var viewModel = ItemListViewModel()
@Environment(\.modelContext) private var modelContext
@State private var refreshCount = 0
var body: some View {
NavigationSplitView {
Group {
switch viewModel.state {
case .idle:
EmptyView()
case .loading:
ProgressView()
case .failed(let error):
Text("Error: \(error)")
case .loaded(let items):
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
}
}
}
#if os(macOS)
.navigationSplitViewColumnWidth(min: 180, ideal: 200)
#endif
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
ToolbarItem {
Button {
do {
try modelContext.save()
} catch {
fatalError(error.localizedDescription)
}
} label: {
Label("Save", systemImage: "square.and.arrow.down")
}
}
ToolbarItem {
Button {
refreshCount += 1
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
}
}
}
} detail: {
Text("Select an item")
}
.task(id: refreshCount) {
// Typically the task ID would be tied to a dynamic predicate.
// I have it tied to a UI button (and made the predicate static) for a minimal example.
await viewModel.load(container: modelContext.container, predicate: #Predicate { _ in true })
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
}
Код: Выделить всё
.task(id: predicate) { /* Same call */ }
.task(id: refreshCount) { // Eww...
viewModel.load(container: modelContext.container, predicate: predicate)
}
[*] Я рассмотрел систему потокового уведомления Swift. Я совершенно уверен, что nspersistentstoreremotechange - это то, что я хочу посмотреть. Я просто не могу понять, как/где инициализировать этого наблюдателя. AddObserver запрашивает аннотации Objective-C. Я не думаю, что .publisher (). Snin {} также является решением, потому что я хочу начать мутирующее вызов viewmodel.load () в (сбегающем) закрытии.
Подробнее здесь: https://stackoverflow.com/questions/797 ... is-changed
Мобильная версия