I am trying to implement a FlowLayout similar to what you would have in a UICollectionView.
I followed the guide from objc.io but modified it slightly to support custom spacing.
Even prior to my modifications, the issue is present.
Views < width of the layout flow fine. Views that are > the size of the layout do not.
Here is the code behind the layout:
public struct FlowLayout: Layout {
private let spacing: CGFloat
public init(spacing: CGFloat = 8) {
self.spacing = spacing
}
public func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) -> CGSize {
let containerWidth = proposal.replacingUnspecifiedDimensions().width
let sizes = subviews.map {
let dimensions = $0.sizeThatFits(.unspecified)
return CGSize(width: min(dimensions.width, containerWidth), height: dimensions.height)
}
let layoutSizes = layout(sizes: sizes, spacing: spacing, containerWidth: containerWidth)
return layoutSizes.size
}
public func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) {
let sizes = subviews.map {
let dimensions = $0.sizeThatFits(.unspecified)
return CGSize(width: min(dimensions.width, bounds.width), height: dimensions.height)
}
let offsets = layout(sizes: sizes, spacing: spacing, containerWidth: bounds.width).offsets
for (offset, subview) in zip(offsets, subviews) {
subview.place(
at: CGPoint(x: offset.x + bounds.minX, y: offset.y + bounds.minY),
proposal: .unspecified
)
}
}
private func layout(
sizes: [CGSize],
spacing: CGFloat = 10,
containerWidth: CGFloat
) -> (offsets: [CGPoint], size: CGSize) {
var result: [CGPoint] = []
var currentPosition: CGPoint = .zero
var lineHeight: CGFloat = 0
var maxX: CGFloat = 0
for size in sizes {
if currentPosition.x + size.width > containerWidth {
currentPosition.x = 0
currentPosition.y += lineHeight + spacing
lineHeight = 0
}
result.append(currentPosition)
currentPosition.x += size.width
maxX = max(maxX, currentPosition.x)
currentPosition.x += spacing
lineHeight = max(lineHeight, size.height)
}
return (
result,
CGSize(width: maxX, height: currentPosition.y + lineHeight)
)
}
}
I am expecting the views with text that is larger than the container to expand vertically and wrap its contents.
What am I missing here? VStack and HStack apparently implement this protocol now, and this issue isn't present with them.

sizeThatFits, you are passing.unspecifiedasProposedViewSizeto the subviews, so they return their ideal size. If the ideal size is too wide then you need to try again, passing a customProposedViewSizethat is constrained to the maximum width.ProposedViewSize: "Layout containers typically measure their subviews by proposing several sizes and looking at the responses. The container can use this information to decide how to allocate space among its subviews."