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

It’s been difficult to diagnose, but I finally was able to determine I have this exact issue. Did you ever resolve this?

I have created a very small sample app that demonstrates Preview the issue I had with generics and included a non-generic workaround. The following is from Feedback FB20628195 that I filed yesterday:

SwiftUI Preview crashes when making calls to SwiftData functions from functions that utilize generic parameters. This app uses the exact same code to set up the ModelContainer for both Running and Previewing. In the process of setting up the ModelContainer, it makes a SwiftData call to determine whether or not a particular Item already present in the container. I have supplied two version of the code to do that. One version uses a generic function to make the SwiftData call and the other version uses a non-generic class function to do the exact same thing. Those calls are made at lines 73 and 76 in the code below. With line 76 active, the app runs correctly and the Preview does not crash. However, with line 73 active, the app will still run correctly, but the Preview will crash.

//
//  Test_PreviewApp.swift
//  Test Preview
//
//  Created by Chuck Hartman on 10/11/25.
//

import SwiftUI
import SwiftData

@main struct Test_PreviewApp: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(TestData.modelContainer)
    }
}

struct ContentView: View {
    
    @Environment(\.modelContext) private var modelContext
    @Query private var items: [Item]

    var body: some View {
        List {
            ForEach(items) { item in
                Text("Item: \(item.order)")
            }
        }
    }
}

@Model final class Item : McEntity {
    var order: Int
    
    init(order: Int) {
        self.order = order
    }
    
    class func retrieve(order: Int, in modelContext: ModelContext) -> Item? {
        let fetchDescriptor = FetchDescriptor<Item>(predicate: #Predicate{ $0.order == order } )
        let result = try? modelContext.fetch(fetchDescriptor).first
        return result
    }
}

public protocol McEntity {
    var order: Int { get set }
}

#Preview {
    ContentView()
        .modelContainer(TestData.modelContainer)
}

@MainActor class TestData {
    
    //MARK: - Create in-Memory Container and Populate Test Data Models
    static let modelContainer: ModelContainer = {
        do {
            let schema = Schema([
                Item.self,
            ])
            let modelConfiguration = ModelConfiguration(isStoredInMemoryOnly: true)
            let modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration])
            
            let modelContext = modelContainer.mainContext
            for order in 0..<10 {
                
                // Retrieving a specific Item this way always works when Running but crashes in Preview
                let item = modelContext.retrieve(order: order, from: Item.self)
                
                // However, retrieving a specific Item this way always works for both Running and Preview
//                let item = Item.retrieve(order: order, in: modelContext)
                
                if item == nil {
                    let newItem = Item(order: order)
                    modelContext.insert(newItem)
                }
            }

            return modelContainer
        } catch {
            fatalError("Failed to create model container: \(error.localizedDescription)")
        }
    }()
}

extension ModelContext {
    
    public func retrieve<Entity>(order: Int, from: Entity.Type) -> Entity? where Entity : PersistentModel, Entity : McEntity {
        let fetchDescriptor = FetchDescriptor<Entity>(predicate: #Predicate{ $0.order == order } )
        let result = try? self.fetch(fetchDescriptor).first
        return result
    }
}