SwiftData Unexpected type for CompositeAttribute

I'm getting SwiftData/SchemaProperty.swift:369: Fatal error: Unexpected type for CompositeAttribute CLLocationCoordinate2D in the following situation - this is simplified, but demonstrates the issue.

There are a lot of posts discussing Measurements running into the same issue, and the recommended fix is to turn the Measurement (or CLLocationCoordinate2D in this example) into a computed property and generate it on the fly.

I'm trying to cache instances of CLLocationCoordinate2D, rather than creating new ones every time a View renders, so those work arounds don't work around.

The SwiftData docs seem to indicate that this should be possible. Can anyone recommend a fix?

Thanks in advance.

import SwiftData
import MapKit

@Model
final class foo : Codable {
    
    var bar: SomeStruct
    
    init( bar: SomeStruct ) {
        self.bar = bar
    }
    
    enum CodingKeys : CodingKey {
        case bar
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.bar = try container.decode(SomeStruct.self, forKey: .bar)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(bar, forKey: .bar)
    }

}

struct SomeStruct : Codable {
    var latitude: Double
    var longitude: Double
    
    @Transient
    var clLocation: CLLocationCoordinate2D

    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
        
        self.clLocation = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }
    
    enum CodingKeys : CodingKey {
        case latitude, longitude
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.latitude = try container.decode(Double.self, forKey: .latitude)
        self.longitude = try container.decode(Double.self, forKey: .longitude)

        self.clLocation = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(latitude, forKey: .latitude)
        try container.encode(longitude, forKey: .longitude)
    }
}

FWIW, here's the code that actually throws the error from the above model.

    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            foo.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
        
        do {
            let container = try ModelContainer(for: schema, configurations: [modelConfiguration])
            container.mainContext.autosaveEnabled = true
            print("DragAlert.sharedModelContainer initialized.")
            return container
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

Questions

1 Why are you using Codable with a @Model type?

2 Why are you not using the SwiftData relationship attribute to define the relationship between foo and some struct or vice versa?

If you're transmitting foo or some struct as JSON over the wire, then you shouldn't mix the swift data models with codables.

  1. When I remove Codable support from SomeStruct, I get this error. Note that "Location" in the image is "SomeStruct" in the example above.

  1. I'm intending to inline the storage for Some Struct into Foo, rather than have a relationship. The struct is only a collection of Doubles. It gets used in multiple Model objects, in some cases for multiple variables. Creating it as a relationship requires that Location have storage for a pointer to the inverse. I don't see how that could work if some class has two separate variables which each have a relationship to the same class. Also, this data is really part of the Foo class in the example, and splitting it out into a separate table doesn't make sense.

Thanks for the engagement.

The error message isn't very clear, but I think what it's trying to convey is that the type you're using isn't compatible with SwiftData (at least not yet). The reason that others have said to use a computed property instead is because you can save the data you actually need to recreate the type that doesn't work.

You're already halfway doing that. I think if you were to remove the @Transient and move the clLocation assignment from your initializer into a computed property, it would work.

struct SomeStruct : Codable {
    var latitude: Double
    var longitude: Double
    
    // doesn't need to be @Transient if it's computed
    var clLocation: CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }

    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }

You did mention that

I'm trying to cache instances of CLLocationCoordinate2D, rather than creating new ones every time a View renders, so those work arounds don't work around.

I don't think it's all that expensive to create the CLLocationCoordinate2D as a computed property when you already have the latitude and longitude handy, but I could be mistaken.

SwiftData Unexpected type for CompositeAttribute
 
 
Q