36

I'm trying to create a simple dynamic list grouped into sections. (SwiftUI iOS13 Xcode11 beta 2)

A simple static example would be :

struct StaticListView : View {
    var body: some View {
        List {
            Section(header: Text("Numbers"), footer: Text("...footer...")) {
                Text("1")
                Text("2")
                Text("3")
            }
            Section(header: Text("Letters"), footer: Text("...footer...")) {
                Text("a")
                Text("b")
                Text("c")
            }
        }
    }
}

This displays as expected a nice list with section headers and footers

But when I try to do this from a dynamic list like this :

struct TestData: Identifiable {
    var id = UUID()
    var title: String
    var items: [String]
}

struct ListView : View {
    let mygroups = [
        TestData(title: "Numbers", items: ["1","2","3"]),
        TestData(title: "Letters", items: ["A","B","C"]),
        TestData(title: "Symbols", items: ["€","%","&"])
    ]
    var body: some View {
        List (mygroups) { gr in
            Section(header: Text(gr.title),
                    footer: Text("...footer...") ) {
                ForEach(gr.items.identified(by: \.self)) { item in
                    Text(item)
                }
            }
        }
    }
}

The result is a list with only 3 rows. Both the Section header, all the content cells and the footer are combined horizontally into a single row.

What am I missing?

Bo Frese
  • 893
  • 1
  • 8
  • 9

4 Answers4

67

Giving List a set of items seems to make it incorrectly treat Section as a single view.

You should probably file a radar for this, but in the meantime, this will give you the behavior you're looking for:

struct ListView : View {
    let mygroups = [
        TestData(title: "Numbers", items: ["1","2","3"]),
        TestData(title: "Letters", items: ["A","B","C"]),
        TestData(title: "Symbols", items: ["€","%","&"])
    ]

    var body: some View {
        List {
            ForEach(mygroups) { gr in
                Section(header: Text(gr.title),
                        footer: Text("...footer...") ) {
                            ForEach(gr.items.identified(by: \.self)) { item in
                                Text(item)
                            }
                }
            }
        }
    }
}

piebie
  • 2,652
  • 21
  • 30
  • 2
    Thanks a lot! Totally solved the problem. I just tried it out on a table with more than 8000 rows in more than 1000 sections. Works like a charm! – Bo Frese Jun 20 '19 at 17:45
  • 1
    I can't believe this worked - no clue why the default behaviour is to just list each section as one row... – Quinn Jul 23 '19 at 18:46
  • This is great - I found that things begin to fall down when you add features like search and delete. – DogCoffee Oct 17 '19 at 20:22
15

Just a small fix for a correct answer above. Since

ForEach(gr.items.identified(by: \.self)) { item in
                            Text(item)
                        }

does not compile as for me, so this do compile and works like a charm:

ForEach(gr.items, id: \.self, content: { item in
                            Text(item)
                        })
Yury
  • 347
  • 3
  • 10
1

While the above solutions work for static data, I'm running into a different situation. In my case, the "mygroups" equivalent is empty when the List is first composed. In the .onAppear{} block, I build the groups.

Once the groups are built I update the @State and the List crashes with this old friend message:

'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (2) must be equal to the number of sections contained in the table view before the update (2), plus or minus the number of sections inserted or deleted (2 inserted, 0 deleted).'

I went from an empty array to one with two sections. I don't think the List is ready yet for such complex dynamic changes (unless there's an API I haven't found yet).

What I'm likely to do is try and build this before the List gets a chance to see it.

P. Ent
  • 1,654
  • 1
  • 12
  • 22
  • So yes - building your list data source before handing it to List is the way to go. Right now, my List does not need to change once built, but I wonder how stable List is if you have operations that add and delete items. For anyone trying to display dynamically composed section lists: first build your data source as arrays of array - don't use Dictionary - and then follow the code in the solutions here to create the List. – P. Ent Nov 22 '19 at 13:32
  • I am experiencing exactly this issue. Found any solutions? – Zappel Nov 03 '20 at 12:37
-2

Embedding the List inside a VStack solved the issue for me.

iOne
  • 21
  • 2