2

Here's a simple example:

struct Example: View {
    var body: some View {
        ScrollView([.horizontal, .vertical], showsIndicators: false, content: {
            LazyVStack(content: {
                ForEach(1...10, id: \.self) { count in
                    Text("Item \(count)")
                }
            })
        })
    }
}

The problem is that when both axes are used ([.horizontal, .vertical]), ScrollView automatically centers any content inside vertically and horizontally. I have a big data table in the ScrollView, and I need it to be aligned to top instead but I couldn't figure out how to do this. Usual stuff with Vstacks and Spacers doesn't work here at all.

Randex
  • 770
  • 7
  • 30
  • Does this answer your question https://stackoverflow.com/a/59739911/12299030? – Asperi Aug 02 '21 at 14:14
  • @Asperi it helps for the X axis, but for Y (which I need) no. When scrolling vertically, items start to suddenly disappear (because of `LazyVStack`, but it happens right in the scrollview, like it ignores the fact that the item that's about to disappear is still visible in the scrollview). I hope my explanation makes sense... – Randex Aug 02 '21 at 15:08

1 Answers1

2

I made an example, with a Slider so you can interactively test it.

This works by making sure the content within the ScrollView is at least as high as the ScrollView itself. This leads to the content filling the whole vertical space, so it will start at the top.

Code:

struct Example: View {
    @State private var count: Double = 10

    var body: some View {
        VStack {
            GeometryReader { geo in
                ScrollView([.horizontal, .vertical], showsIndicators: false, content: {
                    VStack(spacing: 0) {
                        LazyVStack {
                            ForEach(1 ... Int(count), id: \.self) { count in
                                Text("Item \(count)")
                            }
                        }

                        Spacer(minLength: 0)
                    }
                    .frame(width: geo.size.width)
                    .frame(minHeight: geo.size.height)
                })
            }

            Slider(value: $count, in: 10 ... 100)
        }
    }
}

In some cases you may need to use .frame(minWidth: geo.size.width) rather than just width. This can be in the same line as the minHeight.

George
  • 25,988
  • 10
  • 79
  • 133
  • Thank you for your answer! Works great for `VStack`s, but it must work with `LazyVStack` too, because there might be tons of items. – Randex Aug 02 '21 at 15:10
  • @Randex Ah sorry, temporarily changed that. I'll try get it working with `LazyVStack`. – George Aug 02 '21 at 15:16
  • 1
    Works great, thank you very much! Your first solution made me thinking that I need to manually calculate the `minLength` of the spacer, but your final solution turns out to be much cleaner than mine :) – Randex Aug 02 '21 at 15:28
  • 1
    @Randex While trying to figure out, I was getting in a mess with 2 `GeometryReader`s... and then I realised I could make it much simpler doing this – George Aug 02 '21 at 15:30
  • 1
    Ok, so I tested it a bit more in my project where I have a really wide table in that scroll view, and I noticed that when the content is wider than the width of the screen, it doesn't allow you to scroll horizontally because of `.frame(width: geo.size.width)`. To fix that you need to use `minWidth` instead. And we also can put all this into one `.frame` call: `.frame(minWidth: geo.size.width, minHeight: geo.size.height)` – Randex Aug 10 '21 at 17:25
  • Great work! Has anyone got this working with LazyVStack pinned section headers? the top of the header is clipped for me doesn't look right – Denis Murphy Sep 20 '22 at 09:22