SwiftData query with dynamic properties in a View – Swift

by
Ali Hasan
swift-concurrency swift-data

Quick Fix: Inject the desired filter in the view and create the query with the injected filter and default sorting.

The Problem:

How can I create a SwiftUI view that displays data from SwiftData using a query that includes dynamic properties passed into the view?

The Solutions:

Solution 1: Dynamic Queries using Injectable Properties

Instead of using the @Query syntax to define your query directly in the view, you can pass in the dynamic properties (startDate and endDate in this example) as arguments to the view. Then, within the view, you can create the query using these properties and inject it into the view using the Query initializer.

Here’s how you would implement this approach:

struct MyView: View {
    @Environment(\.modelContext) var modelContext
    @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)
    }

    var body: some View {
        Text("Help")
    }
}

In this code, the init method initializes the view with the startDate and endDate values. It then creates a Predicate using these values and injects a Query into the view based on this predicate.

Solution 2: Initializer method

This solution creates an inner class with an initializer that takes a variable passed into the view. The initializer sets up a `Query` with a predicate that includes the variable, allowing it to filter the data displayed in the view. The inner class is then used in the view’s body to display the filtered data. This avoids the limitations of the `@Query` property wrapper and allows for dynamic queries based on variables passed into the view.

Solution 1: Incorporating Filter in Result View

To dynamically customize the filter in your query, you can incorporate your result view as a subview and pass the necessary properties to that subview. By doing so, you can control the filter within the subview’s own @Query. This approach allows you to maintain the benefits of using @Query for data loading while dynamically adapting the filter based on external properties.

Solution 2: Filtering Before Rendering

If the size of your query result is manageable, you can consider filtering it before rendering it into the view. This involves fetching all data from the database and then performing the necessary filtering operations in your code. While this approach may require additional coding, it can improve performance if the query result size is large, as it avoids the overhead of filtering on the database side.

Q&A

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.

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.

How to write ‘inner class’ to show filtered record in list?

Create an inner class with a @Query property and a init that takes the endDate as a parameter. The init should create the predicate and assign it to the _items property.

Can @Query support dynamically changing a querys’s sort order or predicate?

No, @Query does not support dynamically changing a query’s sort order or predicate.

Video Explanation:

The following video, titled "Interactive @Query in SwiftData - Change Query Properties with a ...", provides additional insights and in-depth exploration related to the topics discussed in this post.

Play video

Interactive @Query in SwiftData - Change Query Properties with a Picker (ToDo List App) ... Swift Heroes•9.4K views · 28:17. Go to channel · Ch.