SwiftData @Query crashes when trying to filter or sort using an enum or relationship

Like the title says, I've realised that when I try to use filter or sort on properties that aren't standard supported data types i.e. Using a transformable or a value type like an enum, I seem to be getting the following crash...

SwiftData/DataUtilities.swift:1140: Fatal error: Unexpected type for Expansion: Optional<UIColor>

Xcode expands and shows me when trying to access the wrapped value it's crashing. I'm assumung that the query property wrapper can't handle these custom data types

    @Query private var items: [Item]
{
    get {
        _items.wrappedValue <--- Crash here
    }
}

Which seems to be pointing to a transferable property in one of my models. Below are my two models i'm using.

enum Priority: Int, Codable, Identifiable, CaseIterable {
    case low
    case medium
    case high
    
    var title: String {
        switch self {
        case .low:
            return "Low"
        case .medium:
            return "Medium"
        case .high:
            return "High"
        }
    }
    
    var image: Image? {
        switch self {
        case .medium:
            return Image(systemName: "exclamationmark.2")
        case .high:
            return Image(systemName: "exclamationmark.3")
        default:
            return nil
        }
    }
    
    var id: Self { self }
}

    @Model
    final class Item: Codable {
        var title: String
        @Attribute(originalName: "timestamp")
        var dueDate: Date
        var isCompleted: Bool
        
        var isFlagged: Bool = false
        var isArchived: Bool = false
        
        var isCritical: Bool?

        var priority: Priority?
        
        @Relationship(deleteRule: .nullify, inverse: \Category.items)
        var category: Category?
        
        @Attribute(.externalStorage)
        var image: Data?
        
        enum CodingKeys: String, CodingKey {
            case title
            case timestamp
            case isCritical
            case isCompleted
            case category
            case imageName
        }
        
        
        init(title: String = "",
             dueDate: Date = .now,
             priority: Priority? = nil,
             isCompleted: Bool = false) {
            self.title = title
            self.dueDate = dueDate
            self.priority = priority
            self.isCompleted = isCompleted
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.title = try container.decode(String.self, forKey: .title)
            self.dueDate = Date.randomDateNextWeek() ?? .now
            self.isCompleted = try container.decode(Bool.self, forKey: .isCompleted)
            self.category = try container.decodeIfPresent(Category.self, forKey: .category)
            
            if let imageName = try container.decodeIfPresent(String.self, forKey: .imageName),
               let imageData = UIImage(named: imageName) {
                self.image = imageData.jpegData(compressionQuality: 0.8)
            }
            
            if let isCritical = try container.decodeIfPresent(Bool.self, forKey: .isCritical),
               isCritical == true {
                self.priority = .high
            }
            
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(title, forKey: .title)
            try container.encode(dueDate, forKey: .timestamp)
            try container.encode(isCompleted, forKey: .isCompleted)
            try container.encode(category, forKey: .category)
        }
    }
    
    @Model
    class Category: Codable {
        
        @Attribute(.unique)
        var title: String
        
        var items: [Item]?
        
        @Attribute(.transformable(by: ColorValueTransformer.self))
        var color: UIColor?

        init(title: String = "",
             color: UIColor) {
            self.title = title
            self.color = color
        }
        
        enum CodingKeys: String, CodingKey {
            case title
        }
        
        required init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            self.title = try container.decode(String.self, forKey: .title)
            self.color = UIColor(possibleColors.randomElement()!)
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.container(keyedBy: CodingKeys.self)
            try container.encode(title, forKey: .title)
        }
    }

And below is an example of me sorting based on my enum (Priority) & Relationship (Category name)

func sort() -> [SortDescriptor<Item>]{
    switch self {
    case .title:
        [SortDescriptor(\Item.title)]
    case .date:
        [SortDescriptor(\Item.dueDate)]
    case .category:
        [SortDescriptor(\Item.category?.title)]
    case .priority:
        [SortDescriptor(\Item.priority?.rawValue)]
    }
}

And a filter example below creating a predicate that we will execute to return and matches found in the title or category title

            let highPriority = Priority.high
            if let query {
                return #Predicate {
                    $0.priority == highPriority &&
                    ($0.title.contains(query) || $0.category?.title.contains(query) == true) &&
                    $0.isArchived == false
                }
            }

I'm pretty sure this is a SwiftData bug since when using strings, bools and dates it's all fine using anything outside of that box causes these crashes...

Post not yet marked as solved Up vote post of tundsdev Down vote post of tundsdev
1.6k views
  • Yes I am also facing the same issue, problem is there seems to be no way to compare enum, comparing enum always fails and comparing using rawValue crashes. Could you file a feedback, hopefully it gets fixed. https://developer.apple.com/documentation/swiftdata/preservingyourappsmodeldataacrosslaunches# claims that enum is supported however I am not sure how to get it to work.

  • So they do work when it comes to CRUD opertations. The issues that i’ve found is when you want to filter or sort i’m getting crashes. Also because my model uses transformable this is also causing a issues to. it’s almost as if the Query macro can’t handle this type.

    i’m going to try and see if it crashes using a FetchDescriptor. If it doesnt then that points me in the direction that Query doesn’t play nicely with custom types…

  • Feedback filed FB13202320

Add a Comment

Replies

Ok a bit more info, so it seems that it's more related to the fact that SwiftData doesn't like filtering or sorting on optional values... Below is an example of me filtering based on a non optional string and the fetch request works

            let fetch = FetchDescriptor<Item>(predicate: #Predicate { $0.title == "G" })
            let results = try? modelContext.fetch(fetch)
            print("Found \(results?.count) items") <--- This returns results that match the query

When trying to filter and sort on optional properties like an enum or my property category this is where the issue arises... It seems to crash when trying to access the rawValue and also you get no results when you try to filter based on the case. Below is an example of be executing a query on an optional enum.

            let high: Priority = Priority.high
            let fetch = FetchDescriptor<Item>(predicate: #Predicate { $0.priority == high })
            let results = try? modelContext.fetch(fetch)
            print("Found \(results?.count) items") <--- Returns nothing & accessing the raw value crashes...

I hope someone on the Apple team sees this since it seems SwiftData and optionals just don't play nicely...

This is on Xcode 15 RC

This still is an issue in Xcode Version 15.1 beta (15C5028h)

  • I am convinced this is related to the fact that my transformable is optional. It would be great if the SwiftData team could resolve this.

Add a Comment

I'm also seeing this in Xcode version 15.1 beta 3 (15C5059c).

I have a model (Sensor) with an optional property (end) and the code below causes the app to crash but only on a physical device. It works fine in the simulator with iOS 17.0 or iOS 17.2 but not on a physical device with iOS 17.03.

@Query(filter: #Predicate<Sensor> { sensor in sensor.end == nil }, sort: \Sensor.start, order: .reverse)
private var activeSensors: [Sensor]

If I replace with this, it works fine but it's not the result I'm trying to get at:

@Query var activeSensors: [Sensor]

Still happening in Xcode 15.2...