Learn · 06 of 09
Custom layouts.
TerminalUI's layout protocol follows the SwiftUI contract: the
parent proposes a size, each child chooses its own size, then the
parent places children in a final rectangle. The terminal does not
get a shortcut around that rule.
Use a custom Layout when the built-in stacks, grids,
lists, and tables do not express the relationship between siblings.
Use SendableLayout when the layout value and cache are
safe to run on the frame-tail worker.
The contract
sizeThatFits is measurement. Ask children what they
want with subview.sizeThatFits(...), combine those
answers, and return the size your layout needs. The return value is
still just a request; the parent can later place you in a different
rectangle.
placeSubviews is placement. Use the final
bounds to place every child with
subview.place(at:proposal:). Measurement and placement
are intentionally separate, because scroll views, lists, retained
layout, and async rendering all depend on that boundary.
When to conform to SendableLayout
Plain Layout is always correct. SendableLayout
is an opt-in performance contract: the layout value, its cache, and
everything captured by its callbacks must be safe away from the main
actor. In exchange, the runtime can run measurement and placement on
the frame-tail worker.
The reuse signatures are part of that contract. Include every field
that changes measurement in measurementReuseSignature,
and every field that changes placement in
placementReuseSignature. For the example above,
spacing changes both.
Layout values
Layouts can read child-provided metadata through
LayoutValueKey and .layoutValue(key:value:).
Reach for that when a child needs to say "I am the primary column" or
"I want twice the weight" without coupling the layout to a concrete
child view type.
Where to go next