I’m trying to make a list using LazyVStack
where the content occupies the entire space with a footer that will stay at the bottom of the screen. However, if the list has many items, it should occupy the entirety of the screen with the footer showing only when the user has scrolled to the bottom of the list (so pinned view/sticky footer).
Here's a code snippet to demo what I'm currently seeing:
struct ContentView: View {
@Bindable var viewModel: MockViewModel // Just contains an array of data
var body: some View {
GeometryReader { reader in
ScrollView {
VStack { // Update this to LazyVStack
if viewModel.dataSet.isEmpty {
EmptyListView()
} else {
ForEach(viewModel.dataSet, id: \.id) { viewData in
VStack(spacing: 0) {
DataCell(viewData: viewData)
.background()
.padding(.bottom, 16)
if (viewData.id != viewModel.dataSet.last?.id) {
Divider()
}
}
}
}
/* Footer that would expand to take up any unused space */
VStack(spacing: 0) {
Spacer()
FooterView()
}.frame(maxHeight: .infinity, alignment: .bottom)
}.background()
.frame(minHeight: reader.size.height)
}
}.ignoresSafeArea(edges:.bottom)
}
}
What the list would look like if it was filled with data.
What the list would look like once it reached the bottom.
What the list would look like if it contained only 1 item. This is using the VStack
.
With the code included (using a VStack
), I’m able to achieve the results I’m looking for. However, with a LazyVStack
, it seems like the Spacer
always results in a height of 0. I get the same result as if the same content was placed inside of a List
. Is there a way to achieve what we’re looking for in a LazyVStack
? We rather be using a LazyVStack
to avoid pre-rendering the entire view when it loads.
Any clues would be greatly appreciated! Thank you.
You are setting the minimum height of the LazyVStack
to the height delivered by the GeometryProxy
, but this just adds empty space below the FooterView
. The Spacer
is being made redundant by the LazyVStack
, so it doesn't actually fill the space.
To fix, I would suggest showing the footer in the background of the LazyVStack
using alignment: .bottom
. Also:
To reserve space for the footer, you could either add bottom padding to the LazyVStack
(this works if you know the height of the footer), or else use a hidden copy of the footer as placeholder.
You probably want to add alignment: .top
when setting the minimum height, so that the content is pushed to the top when its height is less than the full height.
GeometryReader { reader in
ScrollView {
LazyVStack {
if viewModel.dataSet.isEmpty {
EmptyListView()
} else {
// ...ForEach as before
}
// Reserve space for footer using a hidden placeholder
FooterView().hidden()
}
// .padding(.bottom, 150) // Alternative way to reserve space
.frame(minHeight: reader.size.height, alignment: .top)
.background(alignment: .bottom) {
FooterView()
}
}
}
.ignoresSafeArea(edges:.bottom)