How to make a KeyPath for NSSortDescriptor generic?

422 Views Asked by At

I am a beginner to programming, so I'd appreciate a beginner-level explanation, if at all possible.

I'm doing Paul Hudson's 100 days of SwiftUI course and we are supposed to make the keyPath parameter for NSSortDescriptor generic. This is inside of a custom view that can be used inside another view to build a list with a Core Data fetch request. I hope that made sense. I can't figure out how to make keyPath generic, such that when I use this in another view I can pass in whatever keypath I want.

struct FilteredList<T: NSManagedObject, Content: View, V: Comparable>: View {
    var fetchRequest: FetchRequest<T>
    var singers: FetchedResults<T> { fetchRequest.wrappedValue }
    var ascendingOrNot: Bool
    var keyPath: KeyPath<T, V>

    let content: (T) -> Content

    var body: some View {
        List(singers, id: \.self) { singer in
            self.content(singer)
        }
    }

    init(filterKey: String, filterValue: String, keyPath: KeyPath<T, V>, ascendingOrNot: Bool, @ViewBuilder content: @escaping (T) -> Content) {
        self.keyPath = keyPath
        self.ascendingOrNot = ascendingOrNot
        fetchRequest = FetchRequest<T>(entity: T.entity(), sortDescriptors: [NSSortDescriptor(keyPath: keyPath, ascending: ascendingOrNot)], predicate: NSPredicate(format: "%K BEGINSWITH %@", filterKey, filterValue))
        self.content = content
    }
}

I tried adding the generic type V and had it conform to Comparable but when I tried to use this in another view, I got the following error: Generic parameter 'V' could not be inferred.

Here is how the view is being used in ContentView

struct ContentView: View {
    @Environment(\.managedObjectContext) var moc
    @State private var lastNameFilter = "A"
    var body: some View {
        VStack {
            // list of matching singers
            FilteredList(filterKey: "lastName", filterValue: lastNameFilter, keyPath: \Singer.lastName, ascendingOrNot: true) { singer in
                Text("\(singer.wrappedLastName)")
            }

            Button("Add Examples") {
                let taylor = Singer(context: self.moc)
                taylor.firstName = "Taylor"
                taylor.lastName = "Swift"

                let ed = Singer(context: self.moc)
                ed.firstName = "Ed"
                ed.lastName = "Sheeran"

                let adele = Singer(context: self.moc)
                adele.firstName = "Adele"
                adele.lastName = "Adkins"

                try? self.moc.save()
            }

            Button("Show A") {
                self.lastNameFilter = "A"
            }

            Button("Show S") {
                self.lastNameFilter = "S"
            }
        }
    }
}

Here is the Singer entity

extension Singer {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Singer> {
        return NSFetchRequest<Singer>(entityName: "Singer")
    }

    @NSManaged public var firstName: String?
    @NSManaged public var lastName: String?
    var wrappedFirstName: String {
        firstName ?? "Unknown"
    }
    var wrappedLastName: String {
        lastName ?? "Unknown"
    }
}

Any assistance is very much appreciated.

0

There are 0 best solutions below