I've implemented a left aligned flow layout using the new iOS 16 Layout
protocol, and I'm using it to add two lists of items to a ScrollView
as follows:
let people = ["Albert", "Bernard", "Clarence", "Desmond", "Ethelbert", "Frederick", "Graeme", "Hortense", "Inigo"]
let places = ["Adelaide", "Birmingham", "Chester", "Dar es Salaam", "East Lothian"]
struct ContentView: View {
var body: some View {
ScrollView(.vertical) {
LeftAlignedFlowLayout {
ForEach(people, id: \.self) { name in
NameView(name: name, colour: .red)
}
}
LeftAlignedFlowLayout {
ForEach(places, id: \.self) { name in
NameView(name: name, colour: .green)
}
}
}
.padding()
}
}
struct NameView: View {
let name: String
let colour: Color
var body: some View {
Text(name)
.font(.body)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.background(Capsule().fill(colour))
.foregroundColor(.black)
}
}
struct LeftAlignedFlowLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let height = calculateRects(width: proposal.width ?? 0, subviews: subviews).last?.maxY ?? 0
return CGSize(width: proposal.width ?? 0, height: height)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
calculateRects(width: bounds.width, subviews: subviews).enumerated().forEach { index, rect in
let sizeProposal = ProposedViewSize(rect.size)
subviews[index].place(at: rect.origin, proposal: sizeProposal)
}
}
func calculateRects(width: CGFloat, subviews: Subviews) -> [CGRect] {
var nextPosition = CGPoint.zero
return subviews.indices.map { index in
let size = subviews[index].sizeThatFits(.unspecified)
var nextHSpacing: CGFloat = 0
var previousVSpacing: CGFloat = 0
if index > subviews.startIndex {
let previousIndex = index.advanced(by: -1)
previousVSpacing = subviews[previousIndex].spacing.distance(to: subviews[index].spacing, along: .vertical)
}
if index < subviews.endIndex.advanced(by: -1) {
let nextIndex = index.advanced(by: 1)
nextHSpacing = subviews[index].spacing.distance(to: subviews[nextIndex].spacing, along: .horizontal)
}
if nextPosition.x + nextHSpacing + size.width > width {
nextPosition.x = 0
nextPosition.y += size.height + previousVSpacing
}
let thisPosition = nextPosition
print(thisPosition)
nextPosition.x += nextHSpacing + size.width
return CGRect(origin: thisPosition, size: size)
}
}
}
The LeftAlignedFlowLayout
works as expected, returning the correct heights and positioning the subviews correctly, but the two layouts are overlapping:
I've tried embedding the two LeftAlignedFlowLayout
in a VStack
, with the same result.
If I add another View between the two layouts, e.g.
LeftAlignedFlowLayout {
...
}
Text("Hello")
LeftAlignedFlowLayout {
...
}
I get the following result:
which seems to show that the correct size is being returned for the layout.
Any thoughts as to how to resolve this issue?