In my application I have defined the following Protocol, which is intended to make all SwiftData models that inherit the NameFilter Protocol to define var name: String exits.
@Model class Session: NameFilter { var name: String var x: .... var y: ... }
import Foundation
protocol NameFilter{
associatedtype String
var name: String {get set}
}
My Session DataModel class
import Foundation
import SwiftData
@Model final class WCSession: NameFilter {
var id: String = UUID().uuidString
var name: String = "" //i.e. Spring 2024
var number: String = "" // 133
var startDate: Date = Date.distantPast // Jun 1
var isCurrent: Bool = false // True
init(name: String, number: String, startDate: Date, isCurrent: Bool) {
self.name = name
self.number = number
self.startDate = startDate
self.isCurrent = isCurrent
}
}
My Generic View
import SwiftUI
import Observation
import SwiftData
struct NavigationStackToolbar<T>: View {
// Bring in my Observable ApplicationData
@Environment(ApplicationData.self) var appData
// Bring in my modelContext defined as dbContext
@Environment(\.modelContext) var dbContext
// Setup my DataQuery of type T
@Query var listItems: [T]
// passed in dataModel from Caller
@State var dataModel: String
// Our Search Query
@State var searchQuery = ""
// Define NameFilter Protocols
@State var name: String = ""
// Setup filter -> Pass in Query Filter or Build here with helpers
var filteredItems: [T] {
if searchQuery.isEmpty {
return listItems
}
// Error on line below : Generic parameter 'ElementOfResult' could not be inferred
// ***********************************************VVVVVVVV *********
let filteredItems = listItems.compactMap { item in
let nameContainsQuery = (item.name as String).range(of: searchQuery, options: .caseInsensitive) != nil
return(nameContainsQuery) ? listItems : nil
}
return filteredItems
}
var body: some View {
NavigationStack(path: Bindable(appData).viewPath) {
List(filteredItems) { item in
HStack {
CellSession(item: item)
}
}
}
}
}
#Preview {
var filterSessions: [WCSession] {
let filterSessions = listSessions.compactMap { session in
let nameContainsQuery = session.name.range(of: searchQuery, options: .caseInsensitive) != nil
return (nameContainsQuery) ? session : nil
}
}
NavigationStackToolbar<Any>(dataModel: "Session", searchQuery: filterSessions)
.environment(ApplicationData())
}
I realize that I need to help the compile know the type by type casting the value.
I have tried the following but can make it work
let nameContainsQuery = (item.name as String).range(of: searchQuery, options: .caseInsensitive) != nil
//. item.name is indicated with the error --> X Value of type 'T' has no member 'name'
The idea in this design is to simplify the view so that is is broken down into parts that can be quickly type checked. I get error stating Compiler ran out of time type checking view. Please break it down into small components ...
I want to create a prototype Model that has a generic Filter on the Model Properity Name which is of type string. Any SwiftData Model that inherits NameFilter would be eligable to filter that view by Name Generically
I am thinking now that code:
struct NavigationStackToolbar<T> : View
Informs the complile that we are sending Type T, inherits NameFitler, but the error indicates that Type T has no memeber Name...
Is there away to write code so that we can force the type cast?
found this link: https://forums.swift.org/t/when-generic-parameter-could-not-be-inferred/18731
This links, discusses Type Alias. Not understanding completly yet.
There are some different issues that needs to be fixed, first the associated type for the protocol makes no sense (unless you really want it to be generic with a generic type named String)
So the protocol becomes
protocol NameFilter{
var name: String {get set}
}
Then your view can't be generic for any type so we need to restrain what T
can be. It should of course conform to NameFilter
but it should also conform to PersistentModel
because that is what @Query
expects.
So add a type alias that combines the two
typealias ModelNameFilter = NameFilter & PersistentModel
And then use that in the view declaration
struct NavigationStackToolbar<T: ModelNameFilter>: View
Now the code in the computed property will compile and run but I believe the code can be simplified to
var filteredItems: [T] {
if searchQuery.isEmpty {
return listItems
}
return listItems.filter { item in
item.name.localizedCaseInsensitiveContains(searchQuery)
}
}