How to align content of SwiftuUI ScrollView to top? (MacOS)

I have a ZStack view embedded in ScrollView. The height of ZStack often changes.

var body: some View {
      ScrollView([.vertical, .horizontal], showsIndicators: true) {
            VStack { // embedding in VStack doesn't change behaviour
                ZStack () {
                    ForEach(mapElements.indices, id:\.self) { i in
                        let element = mapElements[i]
                        MapElementView (mapElement: element)
                          .position(x: offset(x: element.x),
                                    y: offset(y: element.y))
                          .zIndex(5)
                        MakeConnections(mapElement: element)
                    }
                }.frame(width: calculateWidth(),
                        height: calculateHeight())
                }
                Rectangle() // Just to make VStack filled
            }
        }.frame(alignment: .topLeading)
   }

Some scroll views (ie. list) always align embedded objects to top even they are shorter than embedding scroll view. But in this case ZStack is always centred when view is refreshed.

Is it possible to define behaviour, so each change of a content will align its top to scroll view top, even if height of ZStack is smaller than ScrollView?

Accepted Reply

It looks like having both axes in your ScrollView is forcing the content to the center.

Removing .horizontal sends it to the top.

So as a possible fix (here's a minimal example), try:

    var body: some View {
        ScrollView(.horizontal, showsIndicators: true) {
            ScrollView(.vertical, showsIndicators: true) {
                ZStack {
                    Text("content appears at top")
                }
            }
        }
    }

Replies

It looks like having both axes in your ScrollView is forcing the content to the center.

Removing .horizontal sends it to the top.

So as a possible fix (here's a minimal example), try:

    var body: some View {
        ScrollView(.horizontal, showsIndicators: true) {
            ScrollView(.vertical, showsIndicators: true) {
                ZStack {
                    Text("content appears at top")
                }
            }
        }
    }

Has anyone found a solution to getting the contents pinned into the top left that doesn't require embedding two ScrollViews within each other like this?

The problem with this solution is scrolling becomes "locked" to a single direction depending on whichever ScrollView captured the event first. So if scrolling horizontally, you must release the scroll, let it complete, then you are free to scroll vertically. The scroll view area loses diagonal scrolling as well.