Reputation: 910
I'm trying to figure out how to make a SwiftUI view that displays data from SwiftData using a query that includes variables passed into the view. I'm guessing that I won't be able to use the @Query syntax, but has anyone come up with a workable method to do something like this?
Do I need to abandon the @Query and just create a view model that instantiates it's own ModelContainer and ModelContext?
This code is obviously not compiling because the @Query is referencing the startDate and endDate variables, but this is what I want.
struct MyView: View {
@Environment(\.modelContext) var modelContext
@Query(FetchDescriptor<Measurement>(predicate: #Predicate<Measurement> {
$0.date >= startDate && $0.date <= endDate }, sortBy: [SortDescriptor(\Measurement.date)])) var measurements: [Measurement]
let startDate: Date = Date.distantPast
let endDate: Date = Date.distantFuture
var body: some View {
Text("Help")
}
}
Upvotes: 22
Views: 11962
Reputation: 30617
Could try this QueryView
and supply a computed var Query
e.g.
struct QueryView<Model, Content>: View where Model: PersistentModel, Content: View {
@Query var models: [Model]
let content: ([Model]) -> Content
init(query: Query<Model, [Model]>, @ViewBuilder content: @escaping ([Model]) -> Content) {
self.content = content
self._models = query
}
var body: some View {
content(models)
}
}
struct ContentView: View {
var sort: [SortDescriptor<Item>] {
[SortDescriptor(\Item.timestamp, order: ascending ? .forward : .reverse)]
}
var query: Query<Item, [Item]> {
Query(sort: sort)
}
var body: some View {
NavigationSplitView {
QueryView(query: query) { items in
Upvotes: 0
Reputation: 1034
As I learn from hackingwithswift.com: users want to be able to set the sort order (or filter in your case) dynamically, which is not actually supported by @Query right now.
@Query
for data loading) as a subview and customize the @Query
by passing properties into that subview, as suggested by @ingconti.Despite the potential need for unconventional code in the first option, it appears to be the more favorable approach.
Upvotes: 2
Reputation: 911
Here's a wrapper view for @Query that takes a FetchDescriptor, allowing for dynamic predicates, sort order, and limits.
struct DynamicQuery<Element: PersistentModel, Content: View>: View {
let descriptor: FetchDescriptor<Element>
let content: ([Element]) -> Content
@Query var items: [Element]
init(_ descriptor: FetchDescriptor<Element>, @ViewBuilder content: @escaping ([Element]) -> Content) {
self.descriptor = descriptor
self.content = content
_items = Query(descriptor)
}
var body: some View {
content(items)
}
}
Example usage:
struct MeasurementView : View {
@State private var startDate: Date
@State private var endDate: Date
var measurementsDescriptor: FetchDescriptor<Measurement> {
let predicate = #Predicate<Measurement> {
$0.date >= startDate && $0.date <= endDate
}
return FetchDescriptor<Measurement>(predicate: predicate, sortBy: [SortDescriptor(\Measurement.date)])
}
var body : some View {
DynamicQuery(measurementsDescriptor) { measurements in
ForEach(measurements) { measurement in
// ...
}
}
}
}
Upvotes: 4
Reputation: 51973
You can't have a dynamic query (not yet) but a workaround is to inject in the dates (or the full predicate) into the view and create the query that way.
@Query var measurements: [Measurement]
init(startDate: Date, endDate: Date) {
let predicate = #Predicate<Measurement> {
$0.date >= startDate && $0.date <= endDate
}
_measurements = Query(filter: predicate, sort: \.date)
}
Upvotes: 29
Reputation: 11646
my two cents. I wrote an inner class to show filtered record in list:
struct DemoListContentView: View {
@Environment(\.modelContext) private var modelContext
@Query(
FetchDescriptor()
) private var items: [Item]
init(endDate: Date) {
let past = Date.distantPast
let predicate = #Predicate<Item> {
($0.creationDate ?? past) <= endDate
}
_items = Query(filter: predicate)
}
var body: some View {
NavigationView {
VStack{
Text("\(items.count)")
List {
ForEach(items) { item in
ItemCell(item: item)
}
}
}
}
}
}
it will called in:
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@State var lastFetch = Date()
var body: some View {
ListContentView(endDate: lastFetch)
}
}
Hope can help.
Upvotes: 1