Search code examples
iosswiftxcodecore-dataswiftui

How do I use a Fetch Request for multiple entities in CoreData?


Goal: I want to to show a list of Todo items by their due dates which I have already achieved. But I also wanna show a list of a different entity, Categories on the same View above the list of Todo items. The categories are buttons to take you to a list of todo items filtered to that category. I set a relationship of Category to have many todos. How do I change my FetchRequest to support the added relationship?

Here is the current SectionedFetchRequest below. If I try to add a new FetchRequest for Categories I get a crash.

    @SectionedFetchRequest(entity: Todo.entity(),
                           sectionIdentifier: \.dueDateRelative,
                           sortDescriptors: [NSSortDescriptor(keyPath: \Todo.dueDate, ascending: true)],
                           predicate: nil,
                           animation: Animation.linear)
    var sections: SectionedFetchResults<String, Todo>
                    ForEach(sections) { section in
                        Section(header: Text(section.id.description)) {
                            ForEach(section) { todo in
                                TodoRowView(todo: todo)
                                    .frame(maxWidth: .infinity)
                                    .listRowSeparator(.hidden)
                            }
                            .onDelete { indexSet in
                                deleteTodo(section: Array(section), offsets: indexSet)
                            }
                        }
                    }
// Causes Crash when added to existing Fetch Request
//@FetchRequest(entity: Category.entity(), sortDescriptors: []) var categories: FetchedResults<Category>

enter image description here

enter image description here


Solution

  • You might be overthinking this. If you add a sectionIdentifier for the category you can offer the option relatively easy.

    The variable would look like this

    extension Todo{
        @objc
        var categoryTitle: String{
            self.relationship?.title ?? "no category"
        }
    }
    

    Then add a few variables to the View

    //Dictionary to store sort options
    let sortOptions: [String: KeyPath<Todo, String>] = ["due date": \Todo.dueDateRelative, "category":\Todo.categoryTitle]
    //Variable to use to filter list
    @State var selectedSection: String = ""
    //filter the `sections` by the selected section
    //You can use nsPredicate too
    var filteredSections: [SectionedFetchResults<String, Todo>.Element] {
        sections.filter({ val in
            if !selectedSection.isEmpty {
                return val.id == selectedSection
            }else{
                return true
            }
        })
    }
    

    Then give the user some buttons to select

    //Give the user sort options
    Menu("sort"){
        ForEach(Array(sortOptions.keys).sorted(by: <), id:\.self, content: { key in
            Button(key, action: {
                sections.sectionIdentifier = sortOptions[key]!
                selectedSection = ""
            })
        })
    }
    //Give the user section options
    Picker("sections", selection: $selectedSection, content: {
        ForEach(sections, content: {section in
            Text(section.id).tag(section.id)
        })
        Text("all").tag("")
    }).pickerStyle(.segmented)
    

    The ForEach would display the user selections automatically

    ForEach(filteredSections) { section in