Home > OS >  SwiftUI: Put List into ScrollView (get List's content height)
SwiftUI: Put List into ScrollView (get List's content height)

Time:09-17

I am trying to put a List into ScrollView.

If you want to know why, here is my explanation, if not - just skip to the code piece. I need to have a smoothly scaling header on top my List. In order to do that, I should put both the header and the List onto a ScrollView and do the rest using scroll content offset. I also need to forbid List's built-in scroll, since it is already on a ScrollView, and fix the height for the List - so the scroll view knows its content height. It works great, but the height of the List is sometimes(!) wrong.

I can't use a LazyVStack instead of a List - it is slower (no cell reuse I guess), and the List is big. It also breaks NavigationLinks, so it's out - it has to be a List.

The height of cells isn't fixed, so I can't just calculate manually.

Here is my code to create an unscrollable List with fixed height, using UITableView:

struct AutosizingList<Content: View>: View {

    @ViewBuilder var content: Content
    @State private var tableContentHeight: CGFloat = 0

    var body: some View {
        List {
            content
        }
        .introspectTableView { tableView in
            tableView.isScrollEnabled = false
            tableContentHeight = tableView.contentSize.height
        }
        .frame(height: tableContentHeight)
    }
}

I guess this block is not always called after didLayoutSubviews or something, and that's why the height is sometimes correct and sometimes wrong. So here is my question: how do I reliably get List's height? Can I use something remotely analogous to didLayoutSubviews in SwiftUI?

CodePudding user response:

Table contentSize is calculated based on the cells that already appear on the screen. It is assumed that the size of the remaining cells will be the same as the previous ones.

You can see how the scroll indicator changes as you scroll: it may decrease or increase depending on the size of the new cells. In my test, I use a cell height that depends on the index to show this behavior.

In its turn introspectTableView is called only once when creating the view: you can examine the source code.

I suggest you use the key path observing in this case:

struct ContentView: View {
    @State
    private var observation: NSKeyValueObservation?

    var body: some View {
        List {
            ForEach(0..<100) { i in
                Text(String(i))
                    .frame(height: CGFloat(i))
            }
        }.introspectTableView { tableView in
            print("initial size", tableView.contentSize)
            observation = tableView.observe(\.contentSize) { tableView, value in
                print("new size", tableView.contentSize)
            }
        }
    }
}
  • Related