swiftswiftuiswiftui-listswiftui-foreach

How to section a list view in SwiftUI


I am working through "100 Days of SwiftUI in Hacking with Swift. I have a list of expense items that I want to section out, so Personal is one set of items and Business is another for example.

var body: some View {
    NavigationView {
        List {
            ForEach(expenses.items) { item in
                Section(header: Text(item.type)) {
                    HStack {
                        Text(item.name)
                        Text(String(item.amount))
                            .frame(maxWidth: .infinity, alignment: .trailing)
                    }
                }
            }
        }
    }
}

With this current code each item becomes its own segment; which makes sense my logic is not great since the for each apply the section to the item at the current iteration of the loop.

I was looking at adding another for each to go through my array and get the expense types but it does not seem to be a valid way to do it; I get some errors that it is a string and not something to iterate over, it seems logically wrong too. My thought was to do a new view for the expense items and then maybe a conditional to decide what section it should go in but not sure. Working on implementing that but figure to ask while I work on trying it out.


Solution

  • Based on your last comment:

    I want my item.type as the section headers in this case

    I would suggest you have an outer ForEach that iterates over the types and an inner ForEach that iterates over the items. The Section are created at the outer level, the rows of the List are created inside the inner ForEach.

    private var expenseTypes: [String] {
        Array(Set(expenses.items.map { $0.type } )).sorted()
    }
    
    List {
        ForEach(expenseTypes, id: \.self) { type in
            Section(header: Text(type)) {
                ForEach(expenses.items) { item in
                    if item.type == type {
                        HStack {
                            Text(item.name)
                            Text(String(item.amount))
                                .frame(maxWidth: .infinity, alignment: .trailing)
                        }
                    }
                }
            }
        }
    }
    

    BTW, you mentioned in your original question:

    I get some errors that it is a string and not something to iterate over

    This is because String does not implement Identifiable. What you need to do is instruct the ForEach how to form an id for each String item. The code above does exactly this for the array of types. It uses id: \.self, which means, use the string itself as the id. It is worth noting that this technique can often be a cause of error, because if you have duplicate strings in the array then they will have the same id. But in this case, the array is the set of unique types, so it is safe.