SwiftUI 3 introduced a brand spanking new view called ✨ Canvas ✨ for rich and dynamic 2D graphics drawing.
Canvas view passes GraphicsContext and Size values to it closure which can be used to perform immediate mode drawing. Canvas can draw path, image, or complete SwiftUI views inside it.
Note use of context and size inside canvas closure in the code 👆
Here we are using Canvas’s closure to draw ellipse inside a CGRect. Note use of GraphicsContext to draw path for ellipse. We are also using size passed in closure to make sure that our ellipse fits well inside the rect.
Closure in Canvas is not a ViewBuilder like other closures in SwiftUI views, this one is a Swift closure so we can do Swift related operations directly inside the closure and draw on the GraphicsContext.
Lets create an example to perform additional Swifty 😃 operations inside Canvas closure.
struct SimpleCanvasExample: View {
var body: some View {
ZStack {
Canvas { context, size in
let gradient = Gradient(colors: [.blue, .pink, .orange])
let rect = CGRect(origin: .zero, size: size).insetBy(dx: 5, dy: 5)
let path = Path(ellipseIn: rect)
context.stroke(
path,
with: .color(.orange),
lineWidth: 10)
context.fill(path, with: .linearGradient(gradient, startPoint: rect.origin, endPoint: CGPoint(x: rect.width, y: 0)))
}
.frame(width: 300, height: 200)
Text("DevTechie")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
Notice canvas area and use of context.stroke and context.fill functions☝️
So far we have been drawing path and shapes in canvas but canvas can support drawing of text as well.
If you notice, you will realize that “DevTechie” text has been put on Zstack to add text on the top of canvas but what if you want to draw text inside the canvas as well. Lets add following lines to our code:
let midPoint = CGPoint(x: size.width/2, y: size.height/2)
let text = Text("DevTechie")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.white)context.draw(text, at: midPoint, anchor: .center)
Here we are computing midpoint for text to be drawn and then creating TextView with SwiftUI modifiers chained together to format our text. We will store this text view inside a constant and pass it to draw function of GraphicsContext . Our final code will look like this:
struct SimpleCanvasExample: View {
var body: some View {
Canvas { context, size in
let gradient = Gradient(colors: [.blue, .pink, .orange])
let rect = CGRect(origin: .zero, size: size).insetBy(dx: 5, dy: 5)
let path = Path(ellipseIn: rect)
context.stroke(
path,
with: .color(.orange),
lineWidth: 10)
context.fill(path, with: .linearGradient(gradient, startPoint: rect.origin, endPoint: CGPoint(x: rect.width, y: 0)))
let midPoint = CGPoint(x: size.width/2, y: size.height/2)
let text = Text("DevTechie")
.font(.largeTitle)
.fontWeight(.bold)
.foregroundColor(.white)
context.draw(text, at: midPoint, anchor: .center)
}
.frame(width: 300, height: 200)
}
}
We are not using Zstack anymore. 🆒
GraphicsContext’s ResolvedText struct works with text views as well and can be used to resolves a text view. This struct prepares the text to be drawn into the context. ResolveText has size property so any custom drawn text’s size can be computed dynamically using this struct. ResolvedText takes environment values into consideration while drawing, which includes values like display resolution, color scheme etc.
We will also use GraphicsContext’s LinearGradient Shading to add linear gradient to our text view.
struct SimpleCanvasExample: View {
var body: some View {
ZStack {
Canvas { context, size in
let gradient = Gradient(colors: [.blue, .pink, .orange])
let rect = CGRect(origin: .zero, size: size).insetBy(dx: 5, dy: 5)
let linearGradient = GraphicsContext.Shading.linearGradient(gradient, startPoint: rect.origin, endPoint: CGPoint(x: rect.width, y: 0))
let midPoint = CGPoint(x: size.width/2, y: size.height/2)
let font = Font.custom("Chalkduster", size: 54)
var resolvedText = context.resolve(Text("DevTechie").font(font))
resolvedText.shading = linearGradient
context.draw(resolvedText, at: midPoint, anchor: .center)
}
.frame(width: 300, height: 200)
}
}
}
Just like text, images can be drawn on canvas as well. Images can be added by drawing directly on the canvas as shown below:
Canvas can use SwiftUI views with the help of symbols(not SF Symbols 😐). We use combination of GraphicsContext’s resolveSymbol and SwiftUI’s tag to reference SwiftUI Views.