29

Currently building a chat application and I need new messages to appear at the bottom of the screen. I also need to have messages aligned to the bottom. However, using VStack inside ScrollView features top alignment by default.

a busy swiftui

    ScrollView {
        VStack(alignment: .leading, spacing: 16) {
            Spacer()
            .frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
            ForEach(notList, id: \.self) { not in
                NotRow(not: not)
                    
            }
            
        }
        .padding()
        .frame(minWidth: 0, maxWidth: .infinity, minHeight:0, alignment: Alignment.topLeading)
        
    }

What should I do to fix this?

2
  • And you want the empty space to be scrollable ? Commented Sep 15, 2019 at 18:21
  • 1
    I want it to behave like in chat apps: you can do little scrolling but messages return to the bottom. Commented Sep 16, 2019 at 7:49

6 Answers 6

26

ScrollView does not propose the same height to its children (VStack in this case) as it was proposed to it. It just asks for minimal size. The solution is to make its direct child respond with the minimum height which was proposed to ScrollView itself. We can achieve this by reading the proposed height via GeometryReader and setting that as minimum height of the child.

GeometryReader { reader in
    ScrollView {
        VStack {
            /// Spacer() or Color.red will now behave as expected
        }
        .frame(minHeight: reader.size.height)
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

This is a better solution to flipping as the scrollbar is in the expected position. This solution was much needed. Thank you.
This is the only correct answer!
15

A bit late to answer that one but it might help someone else. You could use a good old double rotation trick to achieve the result you want. Other solutions does not work because the maximum size inside a scroll view is undefined as it depends on its child views to compute its own content size.

ScrollView {
  VStack(alignment: .leading, spacing: 16) {
      Spacer()
      .frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
      ForEach(notList, id: \.self) { not in
          NotRow(not: not)
      }

  }
  .padding()
  .rotationEffect(Angle(degrees: 180))
}
.rotationEffect(Angle(degrees: 180))

3 Comments

This will result in the scrollview starting scrolled all the way to the bottom of the content, if the content size is greater than the available space. The better solution is to use a GeometryReader as suggested by @orkhan-alikhanov
Honestly this is such a poggers solution. I approve of this message
Great!, this actually works, unlike the GeometryReader code by @orkhan-alikhanov, which did not move the scroll view to the bottom at all for me, not even a little bit.
6

I think you're missing a maxHeight in VStack

VStack(alignment: .leading) {
  Spacer().frame(maxWidth: .infinity)
  // content here
}
.frame(maxHeight: .infinity) // <- this

1 Comment

Wow, seems kinda inelegant to have to do this. But it worked for me!
3

If you remove Spacer() and put it after the closing braces of your ForEach, it will fix it. Here is how your updated code will look like:

ScrollView {
        VStack(alignment: .leading, spacing: 16) {
            .frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
            ForEach(notList, id: \.self) { not in
                NotRow(not: not)

            }
            Spacer()
        }
        .padding()
        .frame(minWidth: 0, maxWidth: .infinity, minHeight:0, alignment: Alignment.topLeading)

    }

Comments

0

Try changing all of your Alignment.topLeadings to Alignment.bottomLeading

Comments

0

Easy with defaultScrollAnchor() modifier.

ScrollView {
    // ...
}
.defaultScrollAnchor(.bottom)

1 Comment

6 years ago it was not easy..

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.