SwiftData model doesn't work with Transferable and Codable

Hi everyone. I trying to implement some drag and drop functionality together with SwiftData. That requires my model to conform Transferable. And Transferable requires to conform Codable. My code doesn't compile with this error: Type 'Item' does not conform to protocol 'Decodable/Encodable'. The error appears right after I add @Model macro.

Is there a solution or a workaround? Here's my code:

@Model 
final class Item: Transferable, Codable {
    let createdAt: Date
    
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .myCustomType)
    }
    
    init() {
        self.createdAt = .now
    }
}


extension UTType {
    static let myCustomType = UTType(exportedAs: "com.olexgreen.mytype")
}

Thank you very much

Post not yet marked as solved Up vote post of olexgreen Down vote post of olexgreen
1.6k views

Replies

Since @Model is a macro it would do something special (custom logic) for every member property to persist it.

Workaround

  • Create a struct ItemRecord with the same properties
  • Populate ItemRecord with Item
  • Conform ItemRecord to Codable and Transferable
  • Let the Item have a way to conveniently create ItemRecord
  • Use ItemRecord to drag and drop or for other transferable functionalities (item.record)

Code

@Model
final class Item {
    let createdAt: Date
    
    //For convenience
    var record: ItemRecord {
        ItemRecord(item: self)
    }
    
    init() {
        self.createdAt = .now
    }
}

struct ItemRecord: Codable, Transferable {
    let createdAt: Date
    
    init(item: Item) {
        createdAt = item.createdAt
    }
    
    static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .myCustomType)
    }
}

I was also looking for a workaround, but I think I actually came up with a workable solution. I took a completely different approach by not trying to drag and drop the PersistentModel objects themselves, but instead, dragging and dropping the PersistentIdentifier struct from a PersistentModel object. This works well because PersistentIdentifier is already Codable and it is very easy to make it Transferable. Also, a PersistentIdentifier struct can be used to retrieve its corresponding PersistentModel object from the ModelContext.

I have published a complete working sample program demonstrating this on GitHub: https://github.com/Whiffer/SwiftDataDragAndDropExample

The important concepts are:

  • Extend PersistentIdentifier to be Transferable and define your own contentType for it
extension PersistentIdentifier: Transferable {
    public static var transferRepresentation: some TransferRepresentation {
        CodableRepresentation(contentType: .persistentModelID)
    }
}
  • Extend PersistentIdentifier with a generic convenience function to retrieve PersistentModel objects from the ModelContext
extension PersistentIdentifier {
    public func persistentModel<Model>(from context: ModelContext) -> Model? where Model : PersistentModel {
        return context.model(for: self) as? Model
    }
}
  • Use PersistentIdentifier structs with the .draggable() and .dropDestination() View Modifiers
            Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
                .draggable(item.persistentModelID)
                .dropDestination(for: PersistentIdentifier.self) { persistentModelIDs, _ in
                    let targetItem = item   // for clarification
                    for persistentModelID in persistentModelIDs {
                        if let draggedItem: Item = persistentModelID.persistentModel(from: self.modelContext) {
                            print("\(draggedItem.timestamp) dropped on: \(targetItem.timestamp)")
                        }
                    }
                    return true
                }

Improved Solution

After creating my first workaround, I kept thinking about a more direct and generic solution. My ultimate solution is still based on dragging Transferrable PersistentIdentifiers, but does it internally. I have created a Swift Package that implements generic extensions to the .draggable() and .dropDestination() SwiftUI View modifiers to facilitate direct Drag and Drop operations with SwiftData PersistentModel objects.

The Package URL is: https://github.com/Whiffer/swiftdata-transferrable

A complete project that demonstrates how to use this package is available at: https://github.com/Whiffer/SampleSwiftDataTransferrable