iosswiftswiftui-list

Spacer in LazyVStack doesn’t occupy all available space


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.

However, this is what I see with using a LazyVStack. It seems like the Spacer component doesn't expand to fill available space.

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.


Solution

  • 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:

    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)
    

    Screenshot