Skip to content

juanarzola/fetch-descriptor-observer

Repository files navigation

FetchDescriptorObserver

Safely performs SwiftData fetches in the background (at the global executor) as a result of updates in the main viewContext for a FetchDescriptor.

The PersistentModel of these fetches can be mapped to a sendable before sending to the main thread.

Use this when you would have used the @Query macro, but need to process the data from the PersistentModels in the background before converting the result to a sendable for the UI.

Sample usage:

@MainActor
@Observable class ItemTypesObservable {
    // value displayed in the UI
    private(set) var counts: [ItemType: Int] = [:]
    private(set) var error: Error? = nil
    
    // observer of all items. Don't forget @ObservationIgnored. This could also have been 
    // created at the `updates` function, if you need arguments.
    @ObservationIgnored private let observer = FetchDescriptor<Item>(predicate: .true)
        .makeObserver {
            $0.grouped(by: \.itemType).mapValues { $0.count }
        }

    public func updates(_ container: ModelContainer) async {
        do {
            for try await counts in observer.values(container) {
                self.counts = counts
            }
        } catch {
            self.error = error
        }
    }
}

struct ItemTypesView: View {
    @State private var itemTypes = ItemTypesObservable()
    @Environment(\.modelContext) private var modelContext
    var body: some View {
        List {
            let counts = itemTypes.counts
            ForEach(Array(counts.keys), id: \.self) { key in
                let count = counts[key]
                LabeledContent("\(key.description)", value: "\(count ?? 0)")
            }
        }
        .task {
            // keep the observable up-to-date. Automatically cancels when the view disappears, restarts on re-appearance.
            await itemTypes.updates(modelContext.container)
        }
    }
}

Observing background ModelContext updates

Because observers rely on a container's mainContext updating, they won't automatically update when another ModelContext updates the container. To work around this, manually call ModelContainer.postForcedMainContextUpdate to update all observers after other ModelContexts finish their writes.

About

No description or website provided.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages