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)
}
}
With .frame(maxHeight: 1)
the blue padding is gone, but still there is white space between the consecutive HStack
s.
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.
I'm using Xcode 11.3.1 (11C504)
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:
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
}
}
}
A simple addition of
(spacing: 0)
just solved a good hour of futile debugging. This is a life saver