Search code examples
swiftuiforeachswiftdata

ForEach in ForEach, fetching Data


I'm building a View right now, where I try to get several DisclosureGroup with ForEach based on categories. This was the easy part. My problem is the second Array, where I want to fetch items with said category.

I am new to the game and still learning, but I can't figure out how to do it.

Code here:

    @Environment(\.modelContext) var context
    @Query private var category: [CategoryModel]
    @Query private var bonsai: [BonsaiModel]
    
    @State private var searchQuery = ""
    
    var body: some View {
        NavigationStack {
            VStack {
                
            }
            VStack(alignment: .center) {
                List {
                    Section {
                        // Query category to build DisclosureGroup
                        ForEach(category) { categories in // Loop through category
                            DisclosureGroup(categories.name) {
                                
                                ForEach(bonsai) { items in // Loop through bonsai, where bonsai.category = category
                                    Text(items.name)
                                        .bold()
                                    if let category = items.category {
                                        Text(category.name) // show category.title
                                            .foregroundColor(Color(.systemGray))
                                            .bold()
                                            
                                    } else {
                                        Text(" ")
                                            .padding(.top, 4)
                                    }
                                
                                
                                }
                                
                            }
                        }
                        
                        
                    }
                }
                .padding(.horizontal, 8)
                .padding(.top, 8)
                .listStyle(.inset)

I have put two Text-Items in it, to see what data I try to fetch in the "bonsai" ForEach.

Here is a picture, to visualize what I am trying to accomplish:

Right now you see everything, but you can see the categories which should used as a "filter" and only show in their Group.

My guess is, that it will work with compactMap or filter, but I have no clue how I get it to work.

Model here:

@Model
final class BonsaiModel {
    var uuid: UUID
    var timestamp: Date
    var purchased: Date
    var price: Double
    var name: String
    var isFavorite: Bool
    var age: String
    
    @Attribute(.externalStorage)
    var bonsaiImageData: Data?
    
    @Relationship(deleteRule: .nullify, inverse: \CategoryModel.bonsai)
    var category: CategoryModel?
    @Relationship(deleteRule: .nullify, inverse: \SpeciesModel.bonsai)
    var species: SpeciesModel?
    
    init(price: Double = 0.00,
         name: String = "",
         isFavorite: Bool = false,
         age: String = "",
         bonsaiImageData: Data? = nil
    ) {
        self.uuid = UUID()
        self.timestamp = Date()
        self.purchased = Date()
        self.price = price
        self.name = name
        self.isFavorite = isFavorite
        self.age = age
        self.bonsaiImageData = bonsaiImageData

    }
    

}

@Model
final class CategoryModel {
    @Attribute(.unique) var name: String = ""
    
    var bonsai: [BonsaiModel]?
    
    init(name: String) {
        self.name = name

    }
    
}

@Model
final class SpeciesModel {
    @Attribute(.unique) var name: String = ""
    
    var bonsai: [BonsaiModel]?
    
    init(name: String) {
        self.name = name
    }
    
}

Solution

  • Given your model you can directly access the relationship property for the BonsaiModel in CategoryModel, that way you only get the objects that belongs to the current CategoryModel and do not need to perform any filtering or matching of objects.

    ForEach(category) { categories in
        DisclosureGroup(categories.name) {
            ForEach(categories.bonsai ?? []) { items in
                Text(items.name)
                //...
            }
        }
    }
                      
    

    Side note: I would suggest making the to-many side of the relationship none-optional

    var bonsai: [BonsaiModel] = []
    

    also with this solution you can remove the query for BonsaiModel from your view