Skip to content

Building Custom Layout: LayoutValueKey (SwiftUI)

A SwiftUI guide for coding agents. Also covers per view layout parameter, layoutvalue modifier, subview subscript key, custom anchor per child, pass data to layout protocol.

LayoutValueKey attaches a per-view parameter that a custom Layout reads during sizing and placement. The worked example lets each child of a flow layout choose its own anchor point.

Declare the Key and a Sugar Modifier

A key only needs a defaultValue, which SwiftUI uses for views that set nothing. A small View extension hides the verbose layoutValue call.

struct UnitPointKey: LayoutValueKey {
  static var defaultValue: UnitPoint = .center
}

extension View {
  func anchor(_ anchor: UnitPoint) -> some View {
    layoutValue(key: UnitPointKey.self, value: anchor)
  }
}

Attach Values at the Call Site

Children opt in individually; the rest fall back to the default.

FlowLayout {
  Text("Hello")
    .font(.largeTitle)
    .anchor(.bottom)
  Text("World")
    .font(.title)
    .anchor(.top)
  Text("!!!")
    .font(.title3)
}

Read the Value Through the Subview Subscript

Inside the layout, subscript the Subview proxy with the key type. Here the value feeds the anchor argument of place.

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 anchor = subviews[index][UnitPointKey.self]

    subviews[index].place(
      at: CGPoint(
        x: lineX + cache.sizes[index].width / 2,
        y: lineY + cache.sizes[index].height / 2
      ),
      anchor: anchor,
      proposal: ProposedViewSize(cache.sizes[index])
    )

    lineHeight = max(lineHeight, cache.sizes[index].height)
    lineX += cache.sizes[index].width
  }
}

The same mechanism carries any per-child value: a flex factor, a priority, a span. It is the layout-system twin of PreferenceKey, flowing from the child to its layout container.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-building-custom-layout-layoutvaluekey" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems