Xcode Previews, Predicate, and KeyPath issues

There were changes made to Xcode Previews last year that caused my applications to crash relating with SwiftData and KeyPaths when using protocols or generics. It turns out that it was passing an unknown KeyPath from the Predicate macro, making it impossible to determine what table or column I need to evaluate in PredicateExpressions.

Any KeyPaths provided by Predicate is what I expected, but the numeric values after it says “computed” are different and do not match the KeyPaths I have mapped in a dictionary, which is identical to SwiftData’s T.schemaMetadata.

I don’t understand how KeyPaths work, sometimes they show the full path, sometimes they show as computed properties, and sometimes they show as computed properties with different numerical values (but lead to the same destination). I am getting inconsistencies, which makes deriving the tables/columns from KeyPaths very complicated.

I am unable to complete my custom store for my database or use the new Xcode Previews. I’ve been using Legacy Previews Execution, but that also has its own issues that have been fixed with the new one. The Predicate/KeyPath problems do not exist when you run it on device or in simulator either.

It’s been this way since last year with Xcode 16. I made a post on Apple’s Developer Forums already and sent a feedback on it too. But after installing macOS Tahoe and Xcode 26, the issues still haven’t been fixed and I don’t know if they’re actively fixing it at all.

TLDR

  • Using generics and protocols to create FetchDescriptors/Predicates causes the provided KeyPath from PredicateExpressions, Predicate, SortDescriptor, or any KeyPath passed in FetchDescriptor like propertiesToFetch, relationshipKeyPathsForPrefetching, sortBy to pass different KeyPaths that do not match to any of the KeyPaths in the Schema.
  • This results in being unable to identify what the table or column the KeyPath is referring to.

Is there alternative ways to get the type and property names from KeyPath or in Predicate?

What are my options here?

1 Like

A previous example I used (it should crash with the new Xcode Previews).

import SwiftData
import SwiftUI
 
#Preview { ContentView().modelContainer(for: Media.self, inMemory: true) }
 
struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var models: [Media]
    
    var body: some View {
        VStack {
            Button("Fetch") {
                if let model: Media = try? modelContext.fetch(FetchDescriptor(predicate: predicate(id: "test"))).first {
                    print("model: \(model.id)")
                }
            }
            Button("Models: \(models.count)") {
                let media = Media(
                    id: models.contains { $0.id != "test" }
                    ? "test" : UUID().uuidString,
                    type: .image
                )
                modelContext.insert(media)
                try? modelContext.save()
            }
            ForEach(models) { Text($0.id) }
        }
    }
}
 
func predicate<T>(id: String) -> (Predicate<T>) where T: ID {
    let predicate = #Predicate<T> { media in
        media.id == id
    }
    return predicate
}
 
protocol ID: Identifiable where ID == String {
    var id: Self.ID { get }
}
 
@Model class Media: ID, Identifiable {
    @Attribute(.unique) var id: String
    @Attribute var type: MediaType
    
    init(id: String, type: MediaType) {
        self.id = id
        self.type = type
    }
}
 
struct MediaType: Codable, Equatable, Hashable {
    static let image: MediaType = .init(value: 0)
    static let video: MediaType = .init(value: 1)
    static let audio: MediaType = .init(value: 2)
    var value: Int
}

I previously mentioned that this isn't an issue when you run on device or in simulator, but that's probably only true if the build settings is set to DEBUG and not RELEASE.

Can anyone explain why they behave differently?

1 Like