KeylineKit

See the layout SwiftUI actually renders.

KeylineKit draws your preview's layers, padding, spacing, sizes, corner radii and safe area right on the Xcode Canvas, measured in points.

#Preview {
    #Keyline {
        ProfileView()
    }
}
An account screen with every layer named, padding in orange and stack spacing in purple
#Keyline { AccountScreen() }: layer names, padding and spacing.

Measured, not guessed

Every value comes from the rendered layout. When your code says something less precise than a number, KeylineKit shows both, so you learn what default really means.

In your codeOn the CanvasWhy
.padding(24)24The measured value matches the source.
.padding()16 · defaultSwiftUI picked 16; the source said default.
VStack(spacing: Theme.spacing)12 · Theme.spacingThe gap is measured; the constant is named.
.background(.blue, in: RoundedRectangle(cornerRadius: 24))r 24Corner radii are read where they're applied.

Pick what to draw

With no arguments, #Keyline draws layers, padding and spacing. Pass the annotations you want, in any combination, and change their colors with .keylineColors(_:).

.layers

Each layer's type next to its bounds, nested containers included.

.padding

A band for every .padding(...) call, a value per edge and the content outline.

.spacing

The measured gap between siblings of a VStack or HStack.

.size

Width and height dimension lines with dashed extension lines.

.corners

The arc and value of cornerRadius: and .cornerRadius(_:).

.safeArea

Device insets, the safe area and, on iOS 27.1, the reserved regions that cover content.

One macro, three steps

Nothing runs in your app until a preview renders #Keyline.

  1. The macro instruments your views

    At compile time, each view in the closure is wrapped in a node, with probes before and after every .padding(...) call.

  2. Every frame is measured

    At runtime, nodes and probes report their frames with onGeometryChange, also inside NavigationStack and ScrollView.

  3. One canvas draws it all

    A layout step turns frames into bands, gaps and dimension lines, places labels so they never overlap, and a single Canvas draws them.

// What #Keyline turns VStack { ProfileView() }.padding(24) into
_KeylineNode(VStack {
    _KeylineNode(ProfileView(), _KeylineDeclaration("ProfileView"))
}
._keylineProbe(.paddingInner(0))
.padding(24)
._keylineProbe(.paddingOuter(0, declared: "24")), _KeylineDeclaration("VStack", stack: .vertical, spacing: "default"))

Look inside your own views

#Keyline sees the code inside its own closure, so a custom view shows up as a single layer. Add @Keylined to the type and the overlay also draws what its body declares.

When a view and the root of its body share a frame, they share a label: ProfileView › VStack.

@Keylined
struct ProfileView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Image(systemName: "person.crop.circle.fill")
            Text("Taylor Morgan")
        }
        .padding(24)
    }
}

Install

  1. In Xcode, choose File › Add Package Dependencies…
  2. Paste the repository URL and add KeylineKit to your app target.
  3. Import it and wrap a preview in #Keyline.
https://github.com/HelioMartin/KeylineKit.git
  • Xcode 16+
  • Swift 6
  • iOS 16+
  • macOS 13+
  • watchOS 9+
// Package.swift
dependencies: [
    .package(url: "https://github.com/HelioMartin/KeylineKit.git", from: "0.1.0"),
],
targets: [
    .target(name: "MyApp", dependencies: [
        .product(name: "KeylineKit", package: "KeylineKit"),
    ]),
]