Unrecognized selector objectAtIndex

2022-06-21 22:41:30.447763+0200 App[56115:1179524] -[__NSCFSet objectAtIndex:]: unrecognized selector sent to instance 0x600003df9c20

This happened with the following code, basically when trying to do a ForEach on a property coming from a CoreData model:

import CoreData
import SwiftUI

struct BudgetView: View {
    @FetchRequest(entity: CategoryGroup.entity(), sortDescriptors: [])
    var categoryGroups: FetchedResults<CategoryGroup>
    
    var body: some View {
        VStack {
            List {
                ForEach(categoryGroups, id: \.id) { group in
                    Text(group.name)
                    
                    ForEach(group.categories, id: \.id) { category in
                        Text(category.name)
                    }
                }
            }
        }
    }
}

This property is actually declared manually as an array:

public final class CategoryGroup: NSManagedObject, Identifiable {
    @NSManaged public var categories: [Category]
}

But even though I declared it as an array apparently the underlying type that CoreData actually puts there is an NSSet, which by default does not allow to retrieve stuff by its index (does that unrecognized selector objectAtIndex makes a bit more sense now?)

The easy solution is to mark the property as “Ordered” in the CoreData model. The proper solution, however, is to not interact with NSSet directly unless we can avoid it, so we can simply introduce a computed variable that transforms our property into an array (or whatever other data type we need!):

public var categoriesArray: [Category] {
    let set = categories as? Set<Category> ?? []
    return set.sorted {
        $0.id < $1.id
    }
}

This casts the NSSet into an array sorting it by their ID. Now instead of doing the ForEach directly on the categories property we have to use this computed one:

import CoreData
import SwiftUI

struct BudgetView: View {
    @FetchRequest(entity: CategoryGroup.entity(), sortDescriptors: [])
    var categoryGroups: FetchedResults<CategoryGroup>
    
    var body: some View {
        VStack {
            List {
                ForEach(categoryGroups, id: \.id) { group in
                    Text(group.name)
                    
                    /* The change is here */
                    ForEach(group.categoriesArray, id: \.id) { category in
                        Text(category.name)
                    }
                }
            }
        }
    }
}