Previews due to SwiftData Predicates in Xcode 16.3

My preview crashes whenever I compare my model's value to a constant's stored value.

I use struct constants with a stored value as alternatives to enums, (IIRC ever since the beginning) enums never worked at all with .rawValue or computed properties.

This crashes when it runs on Xcode 16.3 beta and iOS 18.4 in previews. It doesn't appear to crash in simulator.

@Model class Media {
    @Attribute(.unique) var id: UUID = UUID()
    @Attribute var type: MediaType
    
    init(type: MediaType) {
        self.type = type
    }
    
    static var predicate: (Predicate<Media>) {
        let image = MediaType.image.value
        let predicate = #Predicate<Media> { media in
            media.type.value == image
        }
        return predicate
    }
}

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
}

My application crashes with "Application crashed due to fatalError in Schema.swift at line 320."

KeyPath \Media.<computed 0x000000034082a33c (MediaType)>.value points to a field (<computed 0x000000034082a33c (MediaType)>) that is unknown to Media and cannot be used.

This appears to also occur if I am using a generic/any func and use protocols to access a property which is a simple String, such as id or name, despite it being a stored SwiftData attribute.

I filed a bug report, but looking to hear workarounds.

Hi @universal-0,

It is surprising that you are encountering such an issue in previews but not when running in the simulator. How are you setting up the SwiftData environment in your preview? Are you able to share your #Preview code?

The crash occurs with just #Preview { ContentView().modelContainer(for:) } when I don't use traits, but I normally use traits, here's one I use:

struct SampleData: PreviewModifier {
    static func makeSharedContext() throws -> ModelContainer {
        let schema = Database.schema
        let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
        let modelContainer = try ModelContainer(for: schema, configurations: configuration)
        return modelContainer
    }
    
    func body(content: Content, context: ModelContainer) -> some View {
        content.modelContext(context.mainContext)
    }
}

I also use this in my traits:

  func body(content: Content, context: ModelContainer) -> some View {
        content.modelContainer(context)
    }

When I removed predicates that accesses a struct's sub property or fetching model in a generic function that lets me access id, then it stops crashing any of my previews with/without traits, I can use SwiftData without issues.

I decided to create a separate project and ran this code below, which was enough to crash previews for my Mac Studio (black screen and a red x mark).

I'm noticing now that it doesn't crash if I remove .modelContainer(_:), but I won't be able to use SwiftData.

import SwiftData
import SwiftUI

#Preview { ContentView().modelContainer(for: Media.self) }

struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query(filter: Media.predicate) private var models: [Media]
    
    var body: some View {
        VStack {
            Button("Models: \(models.count)") {
                let media = Media(type: .init(value: .random(in: 0..<3)))
                modelContext.insert(media)
                try? modelContext.save()
            }
            ForEach(models) { Text($0.id.uuidString) }
        }
    }
}

@Model class Media: Identifiable {
    @Attribute(.unique) var id: UUID = UUID()
    @Attribute var type: MediaType
    
    init(type: MediaType) {
        self.type = type
    }
    
    static var predicate: (Predicate<Media>) {
        let image = MediaType.image.value
        let predicate = #Predicate<Media> { media in
            media.type.value == image
        }
        return predicate
    }
}

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
}

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

I'm gonna see if my iPad does the same...

Also, I mentioned this in my updated bug report, but it turns out I always had this issue since Xcode 16, my preview constantly crashed and using legacy mode fix it. I was able to pin point the cause with Xcode 16.3 (it mentions Fatal Error in Schema.swift in 16.3, instead of Fatal Error in DataUtilities.swift before).

So I wanted to confirm if this crashes with protocols too and it does when I access its id despite it being a stored attribute.

Inserting, then saving, and tapping on fetch results in a crash with the same errors.

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
}

A workaround is to store the Int value for MediaType in Media instead and use a computed property to switch between Int and MediaType

@Model class Media {
    private var type: Int
    var mediaType: MediaType {
        get { MediaType(rawValue: type) }
        set { type = newValue.rawValue } }
    }
    //...
}

Which would give a predicate where we use Int values in the filtering

static var predicate: (Predicate<Media>) {
    let image = MediaType.image.value
    let predicate = #Predicate<Media> { media in
        media.type == image
    }
    return predicate
}

Two other observations, you don't need a id property for your model, the @Model macro makes the type conform to PeristentModel that extends Identifiable so you already have that id property

And secondly I would personally use an enum instead of a struct:

enum MediaType: Int, Codable, Equatable, Hashable {
    case image = 0
    case video
    case audio
}
Previews due to SwiftData Predicates in Xcode 16.3
 
 
Q