Warm tip: This article is reproduced from stackoverflow.com, please click
swiftui

Mysterious spacing or padding in elements in a VStack in a ScrollView in SwiftUI

发布于 2020-04-07 10:23:29

I don't understand why I have extraneous vertical spacing between elements in a ForEach that is inside a VStack that is inside a ScrollView when using a GeometryReader to render a custom horizontal separator line.

        ScrollView {
            ForEach(self.model1.elements, id: \.self) { element in
                VStack.init(alignment: .leading, spacing: 0) {
                    //                    Text("test") // image 3: works correctly
                    //                        .background(Color.blue)
                    GeometryReader { geometry in
                        Path { path in
                            path.move(to: .init(x: 0, y: 0))
                            path.addLine(to: .init(x: geometry.size.width, y: 0))
                        }
                        .strokedPath(.init(lineWidth: 1, dash: [1,2]))
                    }
//                    .frame(maxHeight: 1) // image 2: uncommenting this line doesn't fix the spacing
                    .foregroundColor(.red)
                    .background(Color.blue)

                    HStack {
                        Text("\(element.index+1)")
                            .font(.system(.caption, design: .rounded))
                            .frame(width: 32, alignment: .trailing)
                        Text(element.element)
                            .font(.system(.caption, design: .monospaced))
                            .frame(maxWidth:nil)
                        Spacer()
                    }
                    .frame(maxWidth:nil)
                    .background(Color.green)
                }
                .border(Color.red)
            }
        }

The above code produces this: enter image description here

With .frame(maxHeight: 1) the blue padding is gone, but still there is white space between the consecutive HStacks.
enter image description here

I want the vertical spacing to be like in this image, ie 0. This image is achieved by uncommenting the Text("test") source, and commenting the GeometryReader code. enter image description here

I'm using Xcode 11.3.1 (11C504)

Questioner
andrewz
Viewed
160
Rob Napier 2020-02-01 04:01

If I understand your ultimate goal, it's to have each row bordered above by a dotted line, with no padding between them, like this:

rows separated by red dotted lines

In that case, IMO you should put the lines in a background. For example:

ScrollView {
    VStack(alignment: .leading) {
        ForEach(self.elements, id: \.self) { element in
            HStack {
                Text("\(element.index+1)")
                    .font(.system(.caption, design: .rounded))
                    .frame(width: 32, alignment: .trailing)
                Text(element.element)
                    .font(.system(.caption, design: .monospaced))
                Spacer()
            }
            .background(
                ZStack(alignment: .top) {
                    Color.green
                    GeometryReader { geometry in
                        Path { path in
                            path.move(to: .zero)
                            path.addLine(to: CGPoint(x: geometry.size.width, y: 0))
                        }
                        .strokedPath(StrokeStyle(lineWidth: 1, dash: [1,2]))
                        .foregroundColor(Color.red)
                    }
                }
            )
        }
    }
}

A key point is that ScrollView is not a vertical stacking container. It just takes its content and makes it scrollable. Its content in your code is a ForEach, which generates views; it doesn't really intend to lay them out. So when you combine a ScrollView with a ForEach, nothing is really in charge of placing the views the way you want. To stack views vertically, you want a VStack.

You can apply this to your structure just as well, but adding another VStack, and get the same results:

ScrollView {
    VStack(spacing: 0) { // <---
        ForEach(self.elements, id: \.self) { element in
            VStack(alignment: .leading, spacing: 0) {
                GeometryReader { geometry in
                    Path { path in
                        path.move(to: .init(x: 0, y: 0))
                        path.addLine(to: .init(x: geometry.size.width, y: 0))
                    }
                    .strokedPath(.init(lineWidth: 1, dash: [1,2]))
                }
                .foregroundColor(.red)
                .frame(height: 1)

                HStack {
                    Text("\(element.index+1)")
                        .font(.system(.caption, design: .rounded))
                        .frame(width: 32, alignment: .trailing)
                    Text(element.element)
                        .font(.system(.caption, design: .monospaced))
                        .frame(maxWidth:nil)
                    Spacer()
                }
            }
            .background(Color.green) // <-- Moved
        }
    }
}