19

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

Maxim
  • 201
  • 2
  • 5

3 Answers3

29

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

8HP8
  • 1,720
  • 3
  • 16
  • 15
  • At a glance, this looks nice and works. Sadly, at least with iOS 15, because of this approach, the text views inside VStack can get compressed, resulting in the truncated text. To test: put several blocks of Text views inside the Stack, so it would be one screen height worth of content, some of the Text views would get compressed, some won't in my case. – NeverwinterMoon Dec 10 '21 at 10:19
  • @NeverwinterMoon i just checked and works for me now, maybe it was a bug that got fixed. – passatgt Jul 14 '22 at 08:01
  • Unfortunately this has some weird behavior if it's inside of a NavigationView. – joshuakcockrell Nov 02 '22 at 17:51
12

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.

mahan
  • 12,366
  • 5
  • 48
  • 83
-1

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)
}