Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crash when sorting SwiftData Query with a SortDescriptor made from a KeyPath with an optional relationship in the chain

I have the following models:

@Model
class Game {
    var name: String
    var firstReleasedOn: Date?

    @Relationship(deleteRule: .cascade, inverse: \QueueEntry.game)
    var queueEntry: QueueEntry?

    init(
        name: String,
        firstReleasedOn: Date?
    ) {
        self.name = name
        self.firstReleasedOn = firstReleasedOn
    }
}
@Model
public class QueueEntry {
    var game: Game?
    var createdOn: Date
    var order: Int

    init(order: Int) {
        self.createdOn = .now
        self.order = order
    }
}

I want to populate a List with all entries sorted by their game's release date, so I have the following:

struct QueueView: View {
    @Query(sort: [SortDescriptor(\QueueEntry.game?.firstReleasedOn)])
    private var entries: [LocalData.QueueEntry]
    
    var body: some View {
        List(entries) { entry in
            Text(entry.game.name ?? "")
        }
    }
}

Running the above code in Debug is totally fine. Running in Release, however, throws the following error:

SwiftData/DataUtilities.swift:65: Fatal error: Couldn't find \QueueEntry.<computed 0x00000001049cd3e0 (Optional)>?.<computed 0x00000001049cd440 (Optional)> on QueueEntry with fields [SwiftData.Schema.PropertyMetadata(name: "game", keypath: \QueueEntry.<computed 0x00000001049e3814 (Optional)>, defaultValue: nil, metadata: nil), SwiftData.Schema.PropertyMetadata(name: "createdOn", keypath: \QueueEntry.<computed 0x00000001049e40e8 (Date)>, defaultValue: nil, metadata: nil), SwiftData.Schema.PropertyMetadata(name: "order", keypath: \QueueEntry.<computed 0x00000001049e49ac (Int)>, defaultValue: nil, metadata: nil)]

QueueEntry.game needs to be optional since making it non-optional and trying to create the relationship in the init results in an NSInvalidArgumentException, reasoning explained here by Joakim Danielson.

Is this just a bug/issue with SwiftData that needs to be addressed by Apple? Is there some other workaround I can implement that maintains the integrity of the relationship between Game and QueueEntry?

like image 807
Ben Walker Avatar asked Jan 31 '26 06:01

Ben Walker


1 Answers

This answer doesn't solve the issue with sorting using an optional relationship in the keypath but gives an alternative solution for how to get the expected result.

Since this is about a one-to-one relationship where as mentioned in the comments every QueueEntry must have a Game set even though the relationship is optional on both ends we can modify the view based on this information and work with Game instead.

So using a Game query instead we can change the sort descriptor but we must also add a predicate to filter out and games without a QueueEntry

@Query(filter: #Predicate<Game> { $0.queueEntry != nil }, 
       sort: [SortDescriptor(\Game.firstReleasedOn, order: .reverse)]) private var games: [Game]

This will give us all QueueEntry objects in the database.

Example list

List(games) { game in
    VStack {
        HStack {
            Text(game.name)
            Text(game.queueEntry!.order.formatted())
        }
        Text(game.firstReleasedOn?.formatted() ?? "")
            .font(.caption)
    }
}

As an alternative if one still wants to use QueueEntry objects in the list is to add a computed property and use that for the List

var entries: [QueueEntry] {
    games.compactMap(\.queueEntry)
}

To make it clearer that a QueueEntry always has a Game object we can change the init to take a non-optional Game

init(order: Int, game: Game) {
    self.createdOn = .now
    self.order = order
    self.game = game
}
like image 146
Joakim Danielson Avatar answered Feb 01 '26 20:02

Joakim Danielson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!