SwiftData @Query with #Predicate on Relationship Model – Swiftui

by
Ali Hasan
swift-data swiftui-ontapgesture

Quick Fix: Filter the data manually if the dataset is small. If not, there’s no direct way to use #Predicate on a relationship model in @Query.

The Solutions:

Solution 1: Manual Filtering

Since `#Predicate` doesn’t allow using another `@Model` in the code block, we cannot perform the desired filtering as part of `@Query`. As a workaround, you can manually filter the data in your code. Here’s how you can do it:

  1. Fetch all the pieces using @Query:

    @Query(
        sort: [
            SortDescriptor(\Piece.age, order: .reverse)
        ]
    ) var pieces: [Piece]
    
  2. Create a computed property to filter the pieces based on the selected artist:

    private var filteredPieces: [Piece] {
        return pieces.compactMap { piece in
            guard let artist = piece.artist else {
                return nil
            }
            return artist == selectedArtist ? piece : nil
        }
    }
    
  3. Use the filtered pieces in your SwiftUI view:

    var body: some View {
        List {
            ForEach(filteredItems) { item in
                // Show filtered stuff
            }
        }
    }
    

This way, you can manually filter the pieces based on the selected artist without using `#Predicate`.

Solution 2: Using PersistentModelID

If you cannot use another model in the predicate, you can set a variable with the persistentModelID and use that variable in the predicate. Here’s how you can do it:

  1. Define a Private Query:
@Query private var pieces: [Piece]
  1. Initialize the Query in init():
init(selectedArtist: Artist) {
    let id = selectedArtist.persistentModelID
    let predicate = #Predicate<Piece> { piece in
        piece.artist?.persistentModelID == id
    }

    _pieces = Query(filter: predicate, sort: [SortDescriptor(\Piece.age, order: .reverse)])
}
  • In the init() method, you receive the selectedArtist as a parameter and extract its persistentModelID.

  • Create a predicate using the #Predicate initializer, comparing the artist property of Piece with the id variable.

  • Initialize the _pieces query with the predicate and a sort descriptor for age in reverse order.

  1. Access the Query Results:
var pieces: [Piece] {
    pieces
}
  • Implement a computed property named pieces that returns the results of the query.
  1. Usage:
let pieces = myPieceProvider.pieces
  • You can now use the pieces property to access the filtered results in your views or view models.

Note: This solution relies on the persistentModelID, which is a unique identifier for each managed object. It is a good alternative when you cannot use a relationship model directly in the predicate.

Q&A

Can’t use #Predicate with relationship model

#Predicate does not let you use another @Model in the code block.

Alternative filtering for small datasets

Filter the data yourself and store in a separate array.

Using persistentModelID in predicate

Set a variable with persistentModelID and use that in the 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

How do you get a SwiftData Query to change its attributes / parameters interactively? In this lesson we'll show how you can use a Picker ...