SwiftData Predicate does not handle protocol witness?

Hi,

I have a protocol Record : PersistentModel and my model conforms to this protocol.

protocol Record : PersistentModel
{
   var title: String { get }
}

@Model
final class TestModel
{
   let title: String
}

extension TestModel : Record  {}

I also have genetic view that takes a Record.

 struct GenericView<Element> : View
 where Element : PersistentModel & Record
 {
   @Environment(\.modelContext)
   private var modelContext
       
   @Query
   var elements: [Element]

   var body: some View
   {
      List
      {
         ForEach(elements)
         {
            Text("element: \($0.title)") //works fine
         }
         Spacer()
      }
   }
}

This all works fine until I try to set a filter on the query in a generic way:

init(_ : Element.Type = Element.self, keyword: String)
//where Element = TestModel //works fine if where clause is present
{
     let filter = #Predicate<Element> { element in
       return element.title.contains(keyword) //does not work without the where clause
     //return true //always works
     }
     self._elements = Query(filter: filter)
}

The preview keeps crashing and the message says it can't find
TestModel.<computed ... (String)> on TestModel with PropertyMetadata(name: "title", keypath: \ TestModel.title,

Is this an issue that will always need to be worked around, something that will eventually be fixed/implemented in Swift or did I go wrong somewhere here?

I actually encountered the same exact problem. I find myself needing to make some fetching functions generic to avoid code duplication, but I end up with the same error

Couldn't find \MyType.<computed 0x0000000106269f70 (String)> on MyType with fields [...]

Can someone confirm if this is a bug or now?

I found a workaround for this

Before I had

func findCached<T: MyTypeProtocol>(with something: String) throws -> [T] {
  let fetchDescriptor = FetchDescriptor<T>(
    predicate: #Predicate {
      $0.something == something
    }
  )

  return try context.fetch(fetchDescriptor)
}

Now I don't have the generic type used in the FetchDescriptor. I kept all the generic reusable code I had before, but I also added the following to MyTypeProtocol

protocol MyTypeProtocol: PersistentModel {
  // ...

  static func fetchDescriptorForCachedItem(with: something: String) -> FetchDescriptor<Self>
}

I then implement that in every MyTypeProtocol adopter, where I can finally use a concrete type for the FetchDescriptor.

This approach seems to work, the findCached now looks like this

func findCached<T: MyTypeProtocol>(with something: String) throws -> [T] {
  let fetchDescriptor = T.fetchDescriptorForCachedItem(with: something)

  return try context.fetch(fetchDescriptor)
}

The draw back of this approach is that I need to duplicate the code in fetchDescriptorForCachedItem for every type conforming to MyTypeProtocol but I think is a good compromise given the fact that I manage to have a lot more reusable code in this way.

1 Like