SwiftUI: Spacer doesn't work at ScrollView

Viewed 2713

How can I get space in VStack to pin the button at the bottom?

ScrollView {
        VStack() {
            Text("Title_1")
                .padding(.bottom, 35.0)
            Text("Title_2")
                .padding(.bottom, 32.0)
            Text("Title_3")
                .padding(.bottom, 27.0)
            
            Spacer()
            Button(action: { print("ACTION") }) {
                Text("OK")
                    .font(.title)
                    .fontWeight(.semibold)
                    .foregroundColor(Color.red)
                    
            }
            .frame(height: 35)
            .cornerRadius(8.0)
            .padding(.bottom, 25.0)
        }
        .frame(maxWidth: .infinity)
    }

what I have

what I want to have

3 Answers

The ScrollView gives that VStack infinite possible height. That's why the spacers not working.

The Fix:

  • Give the VStack a minimumHeight that’s the height of the ScrollView or View
  • Also important to set the scrollview's width to be the view's width
GeometryReader { geometry in
    ScrollView(showsIndicators: false) {
        VStack {
        ...
        }
        .frame(minHeight: geometry.size.height)
    }.frame(width: geometry.size.width)
}

THIS WAY THE CONTENT WILL BE ABLE TO LAYOUT BEYOND THE VIEW's HEIGHT IF NEEDED

Use GeometryReader

A container view that defines its content as a function of its own size and coordinate space. https://developer.apple.com/documentation/swiftui/geometryreader

GeometryReader { geometry in
    ScrollView {
        VStack() {
          -----
        }
        .frame(width: geometry.size.width, height: geometry.size.height)
    }
}

Doing so, the VStack is full-screen.


You may not neeed to use ScrollView, because you do not need to scroll to see its content.

    var body: some View {
        
        VStack() {
            Text("Title_1")
                .padding(.bottom, 35.0)
            Text("Title_2")
                .padding(.bottom, 32.0)
            Text("Title_3")
                .padding(.bottom, 27.0)
            
            Spacer()
            Button(action: { print("ACTION") }) {
                Text("OK")
                    .font(.title)
                    .fontWeight(.semibold)
                    .foregroundColor(Color.red)
                
            }
            .frame(height: 35)
            .cornerRadius(8.0)
            .padding(.bottom, 25.0)
        }
        .frame(maxWidth: .infinity)
    }

But if your content's height is more than the screen height, the OK button is at the bottom, regardless. Hence, you do not need to do anything.

Setting minHeight to screen height like other answers suggested works, but it brings issue where the ScrollView will no longer scroll beyond the screen height.

Proper solution is wrapping a VStack outside of the ScrollView.

For your case,

VStack {
    ScrollView {
        VStack() {
            Text("Title_1")
                .padding(.bottom, 35.0)
            Text("Title_2")
                .padding(.bottom, 32.0)
            Text("Title_3")
                .padding(.bottom, 27.0)
        }
        .frame(maxWidth: .infinity)
    }
    Spacer()
    Button(action: { print("ACTION") }) {
        Text("OK")
          .font(.title)
          .fontWeight(.semibold)
          .foregroundColor(Color.red)
    }
    .frame(height: 35)
    .cornerRadius(8.0)
    .padding(.bottom, 25.0)
}
Related