SwiftUI Canvas, Path, Shape, and View

Here are some personal notes about the differences between some basic elements of SwiftUI:

  • a Canvas: a struct supporting immediate mode drawing
  • a Path: a struct defining the outline of a 2D shape
  • a Shape: a protocol/type for an Animatable View defining A 2D shape that you can use when drawing a view
  • a View: a protocol/type that represents part of your interface and provides modifiers

Here's my Playground:

import SwiftUI
import PlaygroundSupport

struct CanvasPathShapeView: View {
    var body: some View {
        VStack {
            HStack {
                VStack {
                    Text("Canvas")
                    Canvas { context, size in
                        context.stroke(
                            Path(ellipseIn: CGRect(origin: .zero, size: size)),
                            with: .color(.green),
                            lineWidth: 4)
                    }
                    .frame(width: 300, height: 200)
                    .border(Color.blue)
                }
                VStack {
                    Divider()
                    Text("Path")
                    Path(ellipseIn: CGRect(origin: .zero, size: CGSize(width: 300, height: 200)))
                        .stroke(.green, lineWidth: 4)
                        .border(Color.blue)
                        .frame(width: 300, height: 200)
                }
            }
            HStack {
                VStack {
                    Divider()
                    Text("Shape")
                    EllipsePath()
                        .stroke(.green, lineWidth: 4)
                        .border(Color.blue)
                        .frame(width: 300, height: 200)
                }
                VStack {
                    Divider()
                    Text("View")
                    EllipseView()
                        .frame(width: 300, height: 200)
                }
            }
        }
        VStack {
            Divider()
            Text("Path with size < frame size")
            Path(ellipseIn: CGRect(origin: .zero, size: CGSize(width: 150, height: 100)))
                .stroke(.green, lineWidth: 4)
                .border(Color.blue)
                .frame(width: 300, height: 200)
        }
        VStack {
            Divider()
            Text("Path without frame")
            Path(ellipseIn: CGRect(origin: .zero, size: CGSize(width: 150, height: 100)))
                .stroke(.green, lineWidth: 4)
                .border(Color.blue)
        }
    }
}

struct EllipsePath: Shape {
    func path(in rect: CGRect) -> Path {
        return Path(ellipseIn: rect)
    }
}

struct EllipseView: View {
    var body: some View {
        GeometryReader { geometry in
            Path(ellipseIn: CGRect(origin: .zero, size: CGSize(width: geometry.size.width, height: geometry.size.height)))
                .stroke(.green, lineWidth: 4)
                .border(Color.blue)
        }
    }
}

let view = CanvasPathShapeView()
PlaygroundPage.current.setLiveView(view)

And the result:

Some notes:

  • Canvas: you can see that the ellipse is clipped, which is hinted at by the documentation: "Use a canvas to draw rich and dynamic 2D graphics inside a SwiftUI view"
  • Path with size < frame size: Canvas, Shape, and View use size readers to match the frame they're in; here, unsurprisingly, the Path here won't expand to fill the frame
  • Path without frame: a Path has no intrinsic size, so without a frame matching its target size, it's clipped
  • Apple's notes about the Canvas limitations are worth being aware of
โš ๏ธ
A canvas doesnโ€™t offer interactivity or accessibility for individual elements, including for views that you pass in as symbols. However, it might provide better performance for a complex drawing that involves dynamic data. Use a canvas to improve performance for a drawing that doesnโ€™t primarily involve text or require interactive elements.

๐Ÿซต What do you make of this?

I'd be curious to hear your thoughts, feedback, and stories on that topic.
Please comment on this tweet, or simply RT/like it for your followers to see.