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 NavigationLink
s, 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)
}
}
}
}