Canvas 是 SwiftUI 中引入的一种强大的绘图视图,允许Swift 代码高效地绘制自定义的图形、路径和视觉效果。它的功能类似于 Core Graphics,但使用了 SwiftUI 风格的声明式语法。
基本结构
1、Canvas 初始化器:
Canvas { context, size in
// 在这里绘图
}
.frame(width: 200, height: 200)
context:表示绘图上下文,用于绘制内容(如路径、文本、图像)。
size:表示 Canvas 的尺寸,绘制内容应基于此动态适配。
常见修饰符:
1)frame(width:height:);
2)background(_:);
3)foregroundStyle(_:);
4)drawingGroup():启用 GPU 渲染和抗锯齿。
常用绘制方法
1、绘制路径
context.stroke(Path(ellipseIn: CGRect(x: 10, y: 10, width: 100, height: 100)), with: .color(.blue), lineWidth: 2)
2、填充路径
context.fill(Path(rect), with: .color(.green))
3、绘制图像
context.draw(Image("photo"), in: CGRect(x: 0, y: 0, width: 100, height: 100))
4、绘制文本
let text = Text("Hello").font(.title)
context.draw(text, at: CGPoint(x: 50, y: 50))
5、绘制带样式的图像
context.stroke(path, with: .linearGradient(Gradient(colors: [.red, .blue]), startPoint: .zero, endPoint: CGPoint(x: 100, y: 100)), lineWidth: 4)
使用示例
1、绘制圆形
Canvas { context, size in
let rect = CGRect(origin: .zero, size: size)
let circle = Path(ellipseIn: rect)
context.fill(circle, with: .color(.blue))
}
.frame(width: 100, height: 100)
2、绘制渐变矩形
Canvas { context, size in
let rect = CGRect(origin: .zero, size: size)
let path = Path(rect)
let gradient = Gradient(colors: [.red, .orange])
context.fill(path, with: .linearGradient(gradient,
startPoint: .zero,
endPoint: CGPoint(x: size.width, y: size.height)))
}
.frame(width: 200, height: 100)
3、绘制文字
Canvas { context, size in
let text = Text("Hello Canvas")
.font(.system(size: 20))
context.draw(text, at: CGPoint(x: size.width / 2, y: size.height / 2))
}
.frame(width: 200, height: 100)
4、绘制圆形和直线
Canvas { context, size in
// 绘制背景
context.fill(
Path(CGRect(origin: .zero, size: size)),
with: .color(.blue.opacity(0.2))
)
// 绘制一个圆形
let circleRect = CGRect(x: size.width / 4, y: size.height / 4, width: size.width / 2, height: size.height / 2)
context.stroke(Path(ellipseIn: circleRect), with: .color(.red), lineWidth: 5)
// 绘制一条对角线
let linePath = Path { path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: size.width, y: size.height))
}
context.stroke(linePath, with: .color(.green), lineWidth: 3)
}
.frame(width: 300, height: 300)

5、动态内容示例:旋转圆圈
struct ContentView: View {
@State private var rotation: Double = 0.0
var body: some View {
TimelineView(.animation) { timeline in
let date = timeline.date.timeIntervalSinceReferenceDate
let rotation = date.truncatingRemainder(dividingBy: 2) * 180
Canvas { context, size in
let radius = min(size.width, size.height) / 2 - 20
let center = CGPoint(x: size.width / 2, y: size.height / 2)
let startAngle = Angle(degrees: rotation)
let endAngle = Angle(degrees: rotation + 270)
var path = Path()
path.addArc(center: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: false)
context.stroke(path, with: .color(.orange), lineWidth: 10)
}
}
.frame(width: 300, height: 300)
}
}

值得注意的是,Canvas 的绘制内容是基于一次性的静态上下文执行的,并不会动态监听状态更新。为了解决这个问题,代码中需要明确地触发视图的更新,因此使用TimelineView视图,使得视图每次刷新时 Canvas 都重新计算绘制内容。
6、使用 TimelineView 创建动态时钟
TimelineView 提供了基于时间的更新机制,可以与 Canvas 配合实现动态时钟。
import SwiftUI
struct ClockView: View {
var body: some View {
TimelineView(.animation) { timeline in
Canvas { context, size in
let now = timeline.date
let calendar = Calendar.current
let second = Double(calendar.component(.second, from: now))
let minute = Double(calendar.component(.minute, from: now))
let hour = Double(calendar.component(.hour, from: now) % 12)
let center = CGPoint(x: size.width / 2, y: size.height / 2)
let radius = min(size.width, size.height) / 2 - 20
// 秒针
let secondAngle = Angle.degrees((second / 60) * 360 - 90)
let secondEnd = CGPoint(
x: center.x + cos(Double(secondAngle.radians)) * radius,
y: center.y + sin(Double(secondAngle.radians)) * radius
)
context.stroke(
Path { path in
path.move(to: center)
path.addLine(to: secondEnd)
},
with: .color(.red), lineWidth: 2
)
// 其他指针可以类似绘制...
}
}
.frame(width: 300, height: 300)
}
}

使用场景
1、动画和动态内容:
与 SwiftUI 的动画系统结合良好,适合创建动态效果。
2、自定义绘图:
如果需要绘制自定义图形、仪表盘、进度条等,Canvas 是理想选择。
3、高性能绘图:
底层使用 Metal 支持,性能优异,适合实时更新的内容。
4、可使用TimelineView:
与 TimelineView 配合,可以创建时间驱动的动画内容。
和Shape的关系
1、Shape 是 SwiftUI 中用来描述几何形状的协议类型。只需要提供路径,它就可以:
1)自动参与布局(支持 .frame、.padding 等);
2)自动适应颜色、动画;
3)与 SwiftUI 的 fill(), stroke() 等配合使用。
struct MyShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.addEllipse(in: rect)
return path
}
}
MyShape()
.fill(Color.blue)
.frame(width: 100, height: 100)
2、Canvas 是一种底层绘图视图,可以直接在一个区域中绘制 Path、Image、Text 等内容。
它更像是 SwiftUI 中的 NSView.draw(_:) 或者 Core Graphics 的替代者,提供自定义绘图功能。
可以实现:
1)使用 GraphicsContext 在屏幕上自由绘制;
2)绘制多个对象、分层绘图、使用滤镜、合成模式等;
3)动态响应状态和动画(比如配合 TimelineView)。
Canvas { context, size in
var path = Path()
path.addRect(CGRect(origin: .zero, size: size))
context.fill(path, with: .color(.blue))
}
.frame(width: 100, height: 100)
3、Shape和Canvas对比
Shape可以绘制几何形状、支持SwiftUI动画,属于View视图类型,自动适配布局系统,不适合复杂绘图(多个图层)。
Canvas可以自由绘图(路径、图像、文本等),支持图层合成、滤镜、阴影、透明度,属于View视图类型,但需要自己控制绘图大小,适合复杂绘图。
总结
Canvas 是 SwiftUI 中用于自定义绘图的现代工具,提供了高效、直观的绘图方式,支持动画和时间驱动的内容。无论是绘制静态图形还是动态内容,都可以很好地满足需求。
相关文章
1、SwiftUI实时更新内容的TimelineView视图:https://fangjunyu.com/2024/12/14/swiftui%e5%ae%9e%e6%97%b6%e6%9b%b4%e6%96%b0%e5%86%85%e5%ae%b9%e7%9a%84timelineview%e8%a7%86%e5%9b%be/
2、Apple图像渲染和计算框架Metal:https://fangjunyu.com/2024/11/17/apple%e5%9b%be%e5%83%8f%e6%b8%b2%e6%9f%93%e5%92%8c%e8%ae%a1%e7%ae%97%e6%a1%86%e6%9e%b6metal/
3、SwiftUI绘制自定义形状的Path:https://fangjunyu.com/2024/12/16/swiftui%e7%bb%98%e5%88%b6%e8%87%aa%e5%ae%9a%e4%b9%89%e5%bd%a2%e7%8a%b6%e7%9a%84path/