29

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

4 Answers 4

51

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

Sign up to request clarification or add additional context in comments.

5 Comments

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 i just checked and works for me now, maybe it was a bug that got fixed.
Unfortunately this has some weird behavior if it's inside of a NavigationView.
Doesn't work in iOS17
Add .fixedSize(horizontal: false, vertical: true) to fix any Text that gets compressed as a result of this
17

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.

Comments

0

From iOS 17 you could use .containerRelativeFrame() modifier.
But it could be tricky for Views with a lot of content for small screens.

ScrollView {
    VStack {
        Spacer()
        
        Text("Hello, world!")
        
        Spacer()
        
        Text("Some Text")
    }
    .containerRelativeFrame(.vertical)
}

More: https://www.hackingwithswift.com/quick-start/swiftui/how-to-adjust-the-size-of-a-view-relative-to-its-container

Comments

-3

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

1 Comment

It won't give your the best user experience

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.