I have three models SwiftData - a parent Event
related to two others. I am trying to write a query that joins the two sub-models and filters to only show the results that match the selected event.
MODELS
@Model
class Event: Identifiable {
var id = UUID()
var name: String = ""
var dateStart: Date = Date()
var dateEnd: Date = Date()
@Relationship(deleteRule: .cascade)
var flights: [Item1]?
var events: [Item2]?
}
@Model
class Item1: Identifiable {
var id = UUID()
var name: String = ""
var dateStart: Date = Date()
var dateEnd: Date = Date()
@Relationship(inverse: \Trip.flights) var trip: Trip?
}
@Model
class Item2: Identifiable {
var id = UUID()
var name: String = ""
var address: String = ""
var notes: String = ""
var dateStart: Date = Date()
var dateEnd: Date = Date()
@Relationship(inverse: \Trip.events) var trip: Trip?
}
All of this is easy enough with a single model. I have a let variable defining the event and then focus on one of the items in the ForEach.
let event: Event
ForEach(event.item1!)
But I can't join the two models into a single variable without a host of errors.
I've tried a basic && in the ForEach but that gives an error that the array can't be converted to expected argument type Bool.
ForEach(event.item1! && event.item2!)
I've also tried a couple of queries but that has it's own errors.
@Query(filter: #Predicate<Item1>{ ($0.trip == trip) }) var item1: [Item1]
@Query(filter: #Predicate<Item2>{ ($0.trip == trip) }) var item2: [Item2]
let mergedEvents = item1 + item2
Each query line gets an error that "Instance member 'event' cannot be used on type 'EventDetails'; did you mean to use a value of this type instead?" And the final variable has an error that "Binary operator '+' cannot be applied to operands of type '[Item1]' and '[Item2]'"
The end result I'm going for is a single list with both Item1 and Item2 on it, sorted by a common field "startDate."
If I could write straight sql I'd write a straightforward JOIN with a WHERE clause to limit the results to the event but I have no idea how to handle it in Swift.
Any suggestions?
You can't join two @Query
or join the results of @Query
directly but what you can do is to create a protocol containing the properties that you want to show in your list and then let the model types conform to the protocol
protocol EventItem {
var id: UUID { get }
var name: String { get }
var dateStart: Date { get }
var dateEnd: Date { get }
}
extension Item1: EventItem {}
extension Item2: EventItem {}
Then you can combine the two properties flights
and events
into an array of type [any EventItem]
Here I did it as a computed property in an extension to Event
extension Event {
var allItems: [any EventItem] {
((events ?? []) + (flights ?? [])).sorted { $0.dateStart < $1.dateStart }
}
}
and then you can use this array in a view
List(event.allItems, id: \.id) { item in
HStack {
Text(item.name)
Spacer()
Text(item.dateStart.formatted(date: .abbreviated, time: .omitted))
Text(item.dateEnd.formatted(date: .abbreviated, time: .omitted))
}
}