Skip to content

Canvas (SwiftUI)

A SwiftUI guide for coding agents. Also covers canvas view drawing, graphicscontext, immediate mode drawing ios, draw text in canvas, resolvesymbol, animate canvas timelineview.

Canvas draws 2D graphics in immediate mode, line by line, instead of composing Shape views that SwiftUI must render individually. Reach for it when a drawing is too rich or too dense for the view tree.

The Renderer

The trailing closure is the renderer. It receives a GraphicsContext and the canvas size, and the context is inout, so state you set affects everything drawn after that line.

Canvas(opaque: true, colorMode: .linear, rendersAsynchronously: false) { context, size in
    context.opacity = 0.3

    let rect = CGRect(origin: .zero, size: size)
    context.fill(Circle().path(in: rect), with: .color(.red))

    let inner = rect.applying(.init(scaleX: 0.5, y: 0.5))
    context.fill(Circle().path(in: inner), with: .color(.red))
}

The context exposes fill, stroke, and clip for paths, plus opacity, scaling, blend mode, and addFilter for effects.

Drawing Text, Images, and Views

Text and Image cannot be drawn directly. Resolve them first; the resolved value exposes shading you can tune before draw.

let text = Text(verbatim: "Hello").font(.largeTitle)
var resolvedText = context.resolve(text)
resolvedText.shading = .color(.red)
context.draw(resolvedText, in: rect)

Arbitrary SwiftUI views go through the symbols closure. Tag each view, then look it up by id in the renderer.

Canvas { context, size in
    let rect = CGRect(origin: .zero, size: size)
    if let symbol = context.resolveSymbol(id: 1) {
        context.draw(symbol, in: rect)
    }
} symbols: {
    Text(verbatim: "Hello")
        .foregroundColor(.red)
        .tag(1)
}

Animation

Canvas does not animate on its own. Wrap it in TimelineView(.animation) and derive drawing parameters from the timeline date.

TimelineView(.animation) { timelineContext in
    let value = secondsValue(for: timelineContext.date)

    Canvas { context, size in
        let newSize = size.applying(.init(scaleX: value, y: 1))
        context.fill(Rectangle().path(in: .init(origin: .zero, size: newSize)),
                     with: .color(.red))
    }
}

Accessibility

A canvas has no accessibility tree, it is raw graphics. Attach the standard accessibility modifiers to the Canvas itself to describe what it shows.

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-mastering-canvas" })
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