Adapting sample code for a "Flow Layout", I built this Layout, to be able to animate changes: (see code block below)
struct MyLayout: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
guard !subviews.isEmpty else {
return .zero
}
// ask subviews for their ideal size
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
var totalHeight: CGFloat = 0
var totalWidth: CGFloat = 0
var lineWidth: CGFloat = 0
var lineHeight: CGFloat = 0
for size in sizes {
if lineWidth > 0 {
if let propWidth = proposal.width {
print("sizeThatFits: proposal.width: \(propWidth)")
if lineWidth + horzSpacing + size.width > propWidth {
totalHeight += lineHeight + vertSpacing // next line + spacing
lineWidth = size.width
lineHeight = size.height
} else {
lineWidth += size.width + horzSpacing // next item + spacing
lineHeight = max(lineHeight, size.height)
}
} else {
lineWidth += size.width + horzSpacing // next item + spacing
lineHeight = max(lineHeight, size.height)
}
} else { // first subview, just place it
lineWidth = size.width
lineHeight = size.height
}
totalWidth = max(totalWidth, lineWidth)
}
totalHeight += lineHeight // first line, zero if there is no first
return .init(width: totalWidth, height: totalHeight)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
var lineX = bounds.minX
var lineY = bounds.minY
var lineHeight: CGFloat = 0
for index in subviews.indices {
let size = sizes[index]
if lineX > bounds.minX {
if let propWidth = proposal.width {
print("placeSubviews: proposal.width: \(propWidth)")
if lineX + size.width > propWidth {
lineY += lineHeight + vertSpacing
lineHeight = 0
lineX = bounds.minX
}
}
}
subviews[index].place(at: .init(x: lineX, y: lineY),
anchor: .zero,
proposal: ProposedViewSize(size)
)
lineHeight = max(lineHeight, size.height)
lineX += size.width + horzSpacing
}
}
}
But this is never called with the space available in proposal, thus it cannot calculate whether the circles would fit adjacent to the squares, or whether they should be moved to the next row.
I wonder how the "Flow Layout" should work at all...
Then I found this explanation about sizeThatFits:
/// the parent view can call this method more than once with different proposals:
/// .zero for the layout’s minimum size
/// .infinity for the layout’s maximum size
/// .unspecified for the layout’s ideal size
which absolutely makes no sense IMHO.
I need the exact (horizontal) space available as input to be able to calculate my layout, and place the subviews...
Of course, I could return oneLine both as ideal and maximum size, and twoLines as minimum size, but it really depends on how much space there is which layout should be chosen, and whether I need to put twoLines into a (horizontal) scrollView or not.
I remember having the same problem 15 years ago when implementing heightForTableViewCell: You tell me how much space I have horizontally, then I can return how much space I need vertically to place my content. Was tricky then, seems still not easy now :-(
The sample code Apple provides for layout:
"Composing custom layouts with SwiftUI"
is completely useless. sizeThatFits should measure the box around the circle layout where the Avatars are placed, but it always returns the same size since that doesn't change at all, only the subview placement changes based on the user input.
So, any tips how I could solve my animation problem?
Topic:
UI Frameworks
SubTopic:
SwiftUI
Tags: