I haven't been able to find a solution to a predicate that filters on an optional one to many relationship. The relationship needs to be optional in order to work with CloudKit. I've recreated this with a very simple project. Let's assume I'm looking for a parents with a kid by the name of 'Abbijean'.
Model.swift
@Model
class Parent {
let identifier: UUID
let name: String
@Relationship(deleteRule: .cascade, inverse: \Kid.parent) var children: [Kid]?
init( name: String, children: [Kid]? = nil) {
self.identifier = UUID()
self.name = name
}
}
@Model
class Kid {
let identifier: UUID
let name: String
var parent: Parent?
init(name: String, parent: Parent? = nil) {
self.identifier = UUID()
self.name = name
}
}
ContentView.swift
struct ContentView: View {
@Environment(\.modelContext) var modelContext
@Query(filter: #Predicate<Parent> { parent in
// parent.children?.contains(where: {
// $0.name == "Abbiejean"
// }) != nil
parent.children.flatMap { children in
children.contains(where: { $0.name == "Abbijean" })
} == true
}) var parents: [Parent]
var body: some View {
Button(action: {
var parent1 = Parent(name: "Sterling Archer")
parent1.children?.append(Kid(name: "Abbijean"))
modelContext.insert(parent1)
}, label: {
Text("Add Data")
})
List {
ForEach(parents, id: \.id) { parent in
Text("\(parent.name)")
}
}
}
}
When using:
parent.children?.contains(where: {
$0.name == "Abbijean"
}) != nil
The compiler tells me to use a flatMap when unwrapping optionals
Using a flat map:
parent.children.flatMap { children in
children.contains(where: { $0.name == "Abbijean" })
} == true
The app crashes with: Thread 1: "to-many key not allowed here"
Tested in Xcode 15.0.1 (iOS 17.0.1) and 15 Beta 3 (iOS 17.2)
A predicate on a to-many relationship isn't possible it seems so you need to divide this into two steps,
First a query on the to-one side of the relationship
@Query(filter: #Predicate<Kid> { child in
child.name == "Abbijean"
}) var children: [Kid]
And then a computed property to get the parents from the children returned by the query
var parents: [Parent] {
Set(children.compactMap(\.parent)).sorted(using: KeyPathComparator(\.name))
}