TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

SOLVED: Group Core Data items by category in List in SwiftUI

Forums > Swift

I have a Core Data container with two entities, a Category and an Item. The Item can have one Category assigned and the Category can be assigned to many Items.

What I need to do is group the items by category in a list in SwiftUI.

The code below doesn't group all items by category, it only shows one item by category. How can I group all items that have the same category assigned under the same category group?

Core Data Entities

Category
    Attributes
        name
    Relationship
        items (Type: To Many)

Item
    Attributes
        name
    Relationship
        category (Type: To One)

Swiftui

struct ItemsView: View {
    let selectedList:List

    @EnvironmentObject private var itemSM: ItemServiceModel

    var body: some View {
        List {
            ForEach(itemSM.items) { item in
                Section(header: Text(item.category?.name ?? "")) {
                    ForEach(itemSM.items.filter { $0.category == item.category }) { filteredItem in
                        Text("\(filteredItem.name ?? "")")
                    }
                }
            }
        }
        .onAppear{
           itemSM.loadItems(forList: selectedList)
       }
    }
}

Service Item Service Model

class ItemServiceModel: ObservableObject{
    let manager: CoreDataManager
    @Published var items: [Item] = []

    func loadItems(forList list: List){
        let request = NSFetchRequest<Item>(entityName: "Item")

        let sort = NSSortDescriptor(keyPath: \Item.name, ascending: true)
        request.sortDescriptors = [sort]

        let filter = NSPredicate(format: "list == %@", list)
        request.predicate = filter

        do{
            items =  try manager.context.fetch(request)
        }catch let error{
            print("Error fetching items. \(error.localizedDescription)")
        }
    }
}

This is what I see, as you can see, only one Fruits & Vegetables section should exist.

3      

Hi @fsdolphin, I'm not sure how you have your List and CoreDataManager set up but you should be able to create the NSFetchRequest by Category instead of by Item and then ForEach over all objects of each category.

List {
    ForEach(itemSM.items) { category in
        Section(header: Text(category.name ?? "")) {
            ForEach(category.items?.allObjects as? [Item] ?? []) { item in
                Text("\(item.name ?? "")")
            }
        }
    }
}

4      

@Hectorcrdna - I have not been able to get it right but I think I'm now closer with your suggestion. If I undrstand you correctly you're suggesting to start iterating the Categories and then drilldown to the items from an other ForEach , correct?

I just need to figure out how am I going to get the categories in SwiftUI since I'm using separate ServiceModels, one for Items and one for Categories, but I feel like I'm closer.

Thanks a lot.

3      

@fsdolphin Correct, instead of using the ItemServiceModel use the CategoryServiceModel, Each Category Object should have a NSSet property for Items since it has a To Many relationship.

3      

It worked. I used the CategoryServiceModel in the first ForEach ForEach(categorySM.categories) { category in... as you suggested and it did the trick. Thanks a lot for your help!

This is what worked for me. It basically returns and displays all categories and its items from the specified list.

CategoryServiceModel

func loadCategories(for list: List) -> ([Category], [Item]) {
    let request = NSFetchRequest<Category>(entityName: "Category")

    let predicate = NSPredicate(format: "ANY items IN %@", list.items ?? [])
    request.predicate = predicate

    let sort = NSSortDescriptor(keyPath: \Category.name, ascending: true)
    request.sortDescriptors = [sort]

    do {
        let categories = try manager.context.fetch(request)
        var categories: [Category] = []
        var items: [Item] = []

        for category in categories {
            categories.append(category)
            if let items = category.items?.allObjects as? [Item] {
                let filteredItems = items.filter { $0.list == list }
                items.append(contentsOf: filteredItems)
            }
        }

        return (categories, items)
    } catch let error {
        print("Error fetching categories. \(error.localizedDescription)")
        return ([], [])
    }
}

SwiftUI

  List {
      ForEach(categorySM.categories) { category in
          Section(header:Text(category.name ?? "")){
              ForEach(category.items?.allObjects as? [Item] ?? []) { item in
                  Text("\(item.name ?? "")")
              }
          }
      }
  }
   .onAppear{
           categorySM.loadCategories(for: selectedList)
    }

4      

Hello,

It seems like you're facing an issue with grouping items by category in SwiftUI when using Core Data. To ensure that all items with the same category are grouped together, you can use the .groupBy modifier along with .fetchRequest to fetch the data correctly. Here's a sample code snippet to achieve this: import SwiftUI import CoreData

struct ContentView: View { @Environment(.managedObjectContext) private var viewContext

var body: some View {
    NavigationView {
        List {
            ForEach(categoriesWithItems, id: \.self) { category in
                Section(header: Text(category.name ?? "")) {
                    ForEach(category.itemsArray, id: \.self) { item in
                        Text(item.name ?? "")
                    }
                }
            }
        }
        .listStyle(InsetGroupedListStyle())
        .navigationTitle("Items Grouped by Category")
    }
}

private var categoriesWithItems: [Category] {
    do {
        let request: NSFetchRequest<Category> = Category.fetchRequest()
        let categories = try viewContext.fetch(request)
        return categories
    } catch {
        return []
    }
}

}

3      

@instapro112 - I tried what you suggested but that only returns the categories, I actually need all the categories and items within the specified list. Thank you very much for your help.

3      

Oops -- you just reinforced the bot! I suspect they are learning which answers are most convincing. The giveaway is the generality of the advice.

3      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.