0
votes

I feel like I'm missing something obvious here, but I've been stuck on this for a couple of days and just can't seem to find the answer.

1.) I'm using a separate swift file with an Identifiable Struct that has an object with 2 of the Struct properties, name & categoryName. (side note, I'm using var instead of let in the object because the rows can't be rearranged with the .onMove modifier as a constant)

    //STRUCT

struct Item: Identifiable {
    var id = UUID()
    var name: String
    var categoryName: String
}



//OBJECT

var items : [Item] = [

    //CLOTHING
    Item(name: "Hats", categoryName: "Clothing"),
    Item(name: "Shirts", categoryName: "Clothing"),
    Item(name: "Pants", categoryName: "Clothing"),

    //Electronics
    Item(name: "Macbook", categoryName: "Electronics"),
    Item(name: "Macbook Adapter", categoryName: "Electronics"),
    Item(name: "iPhone", categoryName: "Electronics"),


]

2.) In a swiftui file I have this code to build the list, using a nested ForEach loop to pull the categoryName, add it to the Section header, then another to loop out the items.

    //List code

 NavigationView {
            List {
                ForEach(items) { currentItem in
                    Section(header: Text(currentItem.categoryName)){
                        ForEach(items) { currentItem in
                             NavigationLink(destination: ItemDetail(itemData: currentItem)){ ItemRow(item: currentItem)

                            }
                        }

Unfortunately what I get is a laughable result.

enter image description here

I get my categoryName in the section header and I get my items listed below it. Actually, I get ALL of the items listed below it, regardless of category. Then in a very confusing fashion the sections will print out exactly as many times as the rows in my object array.

In this instance I have 6, so I get 6 rows. Yet, of the 2 categoryName strings "Clothing" and "Electronics", they'll print out 3 times each.

It feels like there's a simple way to do "for each categoryName in items.categoryName add a title to the section and list the corresponding name" - but I'm not cracking this one.

Hoping someone here can point out what I'm doing wrong.

2

2 Answers

1
votes

You have flat array and just iterate though it several times, so result is also flat multiplied several times.

Ok, for model you selected, ie items, the result you tried to accomplish can be reached with the following...

List {
    ForEach(Array(Set(items.compactMap{ $0[keyPath: \.categoryName] })), id: \.self) { category in
        Section(header: Text(category)) {
            ForEach(items.filter { $0.categoryName == category }) { currentItem in
                 NavigationLink(destination: Text("ItemDetail(itemData: currentItem)")){ Text("\(currentItem.name)") }
                }
            }
        }
}
.listStyle(GroupedListStyle())

However I would select different model for items, ie. dictionary as [categoryName: Item]

1
votes

You can group your array by category:

let sections = Dictionary(grouping: items) { $0.categoryName }

Then you can use it like:

var keys: [String] = { sections.map {$0.key} }

var body: some View {
    List {
        ForEach(keys, id: \.self) { section in
            Section(header: Text(section)){
                ForEach(self.sections[section] ?? []) { currentItem in
                    Text(currentItem.name)
                }
            }
        }
    }
}

Note that I have simplified your code to run on my machine but changes are not effecting the answer