25

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?

Orkhan Alikhanov
  • 9,122
  • 3
  • 39
  • 60
Hekes Pekes
  • 1,175
  • 2
  • 12
  • 28

5 Answers5

14

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)
    }
}
Orkhan Alikhanov
  • 9,122
  • 3
  • 39
  • 60
  • 1
    This is a better solution to flipping as the scrollbar is in the expected position. This solution was much needed. Thank you. – JohnnyD Apr 09 '23 at 19:57
11

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))
Antoine Lamy
  • 863
  • 10
  • 9
  • 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 – JoGoFo Jul 26 '22 at 08:16
5

I think you're missing a maxHeight in VStack

VStack(alignment: .leading) {
  Spacer().frame(maxWidth: .infinity)
  // content here
}
.frame(maxHeight: .infinity) // <- this
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)

    }
Fateh
  • 101
  • 6
0

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

Benj
  • 736
  • 7
  • 21