Pass @Query filter predicate from parent view in SwiftData

Hi all,

I am starting using the new amazing SwiftData and I really like it!

I have a question regarding how to pass the filter predicate from the parent view.

In my app I have a simple case where I have a package that contains multiple items.

When I package is selected a view is pushed with the list of items in that package.

The Items view has a query as following:

struct ScoreListView: View {

    [.....]
    
    @Query(filter: #Predicate<SWDItem> { item in
        item.package?.id == selectedPackage.id
    } ,sort: \.timestamp) var items: [SWDItem]
    
    [.....]
    
    var body: some View {
        
[...]

    }

The problem is that selectedPackage is not "yet" available when defining the @Query and I have the classic error "Cannot use instance member 'selectedPackage' within property initializer; property initializers run before 'self' is available".

How can I structure the code to have the selected package available when the @Query is defined?

Thank you a lot

Answered by BabyJ in 757245022

You must remember that we are only in the second beta of SwiftData and there are loads of bugs still floating around at the moment.

I just had a quick look at the release notes for beta 2 and look what I found:

SwiftData

Known Issues

• #Predicate does not support UUID, Date, and URL properties. (109539652)

So the problem is definitely with the predicate and the fact that you are comparing UUID properties.

The current approach for this would be to move the initialisation of the query to the view's initialiser. Something like this will work:

struct ScoreListView: View {
    @Query private var items: [SWDItem]

    let selectedPackage: SWDItem
    
    init(selectedPackage: SWDItem) {
        self.selectedPackage = selectedPackage

        _items = Query(filter: #Predicate<SWDItem> { item in
            item.package?.id == selectedPackage.id
        }, sort: \.timestamp)
    }
    
    var body: some View {
        ...
    }
}

Yes, this is what I tried as well, but got this error:

I'm a bit confused why I get this.

PublicID is a UUID type.

Hi @BabyJ , thanks for your help.

SWDPackge is as simple as this:

@Model final public class SWDPackage {
    
    public var name: String?
    public var timestamp: Date = Date()
    public var publicID = UUID()
    
    public init(name: String? = nil, timestamp: Date, items: [SWDItem] = []) {
        self.name = name
        self.timestamp = timestamp
        self.items = items
    }
    
    @Relationship(.cascade, inverse: \SWDItem.package)
    public var items: [SWDItem]?
    
}

while for the SWDItem is the following

@Model final public class SWDItem {
        
    public var timestamp: Date = Date()
    public var name: String?
    @Attribute(.unique)
    public var publicID = UUID()
    
    public var value: Double = 0.0
    
    public init(timestamp: Date, value: Double, package: SWDPackage) {
        self.timestamp = timestamp
        self.value = value
        self.package = package
    }
    
    @Relationship
    public var package: SWDPackage?
    
}

I have to put the values that do not have default values as optionals (included relationships) to fulfill iCloud Sync requirements.

I tried with and without @Attribute(.unique). Same results

Accepted Answer

You must remember that we are only in the second beta of SwiftData and there are loads of bugs still floating around at the moment.

I just had a quick look at the release notes for beta 2 and look what I found:

SwiftData

Known Issues

• #Predicate does not support UUID, Date, and URL properties. (109539652)

So the problem is definitely with the predicate and the fact that you are comparing UUID properties.

Deleted

Hopefully we will be able to replace the predicate in a future version of SwiftData, here is how I would like it to work:

struct RemindersList: View {

    let todoList: TodoList

    let query = Query<Reminder, [Reminder]>

    var reminders: [Reminder] {
        let todoListID = todoList.persistentModelID
        
        let predicate = #Predicate<Reminder> { reminder in
            reminder.todoList?.persistentModelID == todoListID
        }
        query.predicate = predicate
        return query.wrappedValue
    }
    
    var body: some View {
        List(reminders) {
            ...
        }
    }
}

Then I would simply be able to do RemindersList(todoList: todoList)

I submitted this as feedback FB12367164

Pass @Query filter predicate from parent view in SwiftData
 
 
Q