SwiftUI calls a custom Layout's functions many times per layout pass, so repeated measurement gets expensive. The protocol's Cache associated type lets you compute subview sizes once and reuse them.
Define a Cache Type
Cache is Void by default. Declare a nested Cache struct, build it in makeCache, and refresh it in updateCache, which SwiftUI calls when the layout's children change. Skip updateCache and SwiftUI rebuilds via makeCache instead.
struct FlowLayout: Layout {
struct Cache {
var sizes: [CGSize] = []
}
func makeCache(subviews: Subviews) -> Cache {
let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
return Cache(sizes: sizes)
}
func updateCache(_ cache: inout Cache, subviews: Subviews) {
cache.sizes = subviews.map { $0.sizeThatFits(.unspecified) }
}
}
What the Cache Functions Cannot Know
makeCache and updateCache receive only the Subviews proxy, not the proposed size or bounds. Positions depend on those, so exact placement cannot be precomputed there. Compute positions in sizeThatFits and placeSubviews, where the cache arrives as an inout parameter you may also write to.
Read the Cache During Placement
placeSubviews uses the cached sizes instead of re-measuring every child, and can store extra derived data back into the cache.
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) {
var lineX = bounds.minX
var lineY = bounds.minY
var lineHeight: CGFloat = 0
for index in subviews.indices {
if lineX + cache.sizes[index].width > (proposal.width ?? 0) {
lineY += lineHeight
lineHeight = 0
lineX = bounds.minX
}
let position = CGPoint(
x: lineX + cache.sizes[index].width / 2,
y: lineY + cache.sizes[index].height / 2
)
lineHeight = max(lineHeight, cache.sizes[index].height)
lineX += cache.sizes[index].width
subviews[index].place(
at: position,
anchor: .center,
proposal: ProposedViewSize(cache.sizes[index])
)
}
}