SwiftUI 支持即时模式绘制视图 - Canvas

前言

在 SwiftUI 中,你可以使用 Shape 的 API 去绘制你所需要的 2D 图形。但最终,SwiftUI 框架会将你绘制的所有图形转换为 SwiftUI 视图并去渲染它们。这种方法有利有弊,当我们需要绘制复杂的图形时,我们需要组合多个简单图形去实现。

但现在,我们可以在不组合多个形状的情况下绘制丰富的 2D 图形。这就需要使用到我们接下来要介绍的 Canvas 视图。

简单使用

Canvas 视图支持即时模式绘制,无需使用 Shape API。我们可以用它来画任何我们想要的东西,以一种程序化的方式,逐行绘制。让我们看一个下面的这个小例子。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
less复制代码struct ContentView: View {
var body: some View {
Canvas(
opaque: true,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
let rect = CGRect(origin: .zero, size: size)


var path = Circle().path(in: rect)
context.fill(path, with: .color(.blue))
}
}
}

效果图如下:

截屏2024-04-19 13.41.05.png

正如你在上面的例子中看到的,我们创建了一个 Canvas 视图作为 ContentView 的根视图。它接受一些参数,允许我们用不透明、颜色模式和异步渲染选项配置画布。

我们应该把所有的绘图逻辑放在传递给 Canvas 视图的闭包中。这个闭包称为渲染器。渲染器闭包为我们提供了一个 GraphicalContext 的实例,我们用它来绘制内容和画布的大小。

GraphicsContext 类型的实例是渲染器闭包的 inout 参数。这意味着我们可以在绘制内容时对其进行适当的修改。比如我们在当前圆形的左上角再绘制一个紫色小圆形,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
less复制代码struct ContentView: View {
var body: some View {
Canvas(
opaque: true,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
context.opacity = 0.3

let rect = CGRect(origin: .zero, size: size)

var path = Circle().path(in: rect)
context.fill(path, with: .color(.red))

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

效果图如下:

截屏2024-04-19 13.42.10.png

如上图所示,我们调整了上下文的不透明度,它影响了该线之后出现的所有绘图逻辑。GraphicsContext 类型允许我们调整许多绘图过程参数,如不透明度、缩放和混合模式。它还允许我们使用 addFilter 函数添加不同的过滤器。

GraphicsContext 类型提供描边、填充和剪辑功能,允许我们绘制任何需要的路径。但它也提供了绘制功能,允许我们绘制文本和图像。

绘制文本和图像

需要注意的是,我们不能直接通过 Canvas 绘制文本或图像类型的实例。我们应该使用 GraphicsContext 类型上的 resolve 函数将它们转换为 draw 函数接受的格式。resolve 函数返回一个 ResolvedTextResolvedImage 类型的实例,它允许我们调整已转换类型对象的阴影。代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
css复制代码struct ContentView: View {
var body: some View {
Canvas(
opaque: true,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
let rect = CGRect(origin: .zero, size: size)
let text = Text(verbatim: "Canvas Text").font(.title)
var resolvedText = context.resolve(text)
resolvedText.shading = .color(.white)
context.draw(resolvedText, in: rect)
}
}
}

效果图如下:
截屏2024-04-19 13.49.28.png

你不仅可以使用 Canvas 类型来绘制文本和图像,还可以绘制任何 SwiftUI 视图。但在此之前,我们应该在创建画布时使用符号闭包来注册它们。符号闭包中的每个 SwiftUI 视图都应该有其唯一的标签,以便我们稍后在渲染器闭包中通过 id 解析视图。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
css复制代码struct ContentView: View {
private let symbolID = 1
var body: some View {
Canvas(
opaque: true,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in

let rect = CGRect(origin: .zero, size: size)

if let symbol = context.resolveSymbol(id: symbolID) {
context.draw(symbol, in: rect)
}
} symbols: {
Text(verbatim: "Canvas Text")
.foregroundColor(.blue)
.tag(symbolID)
}
}
}

效果图如下:
截屏2024-04-19 13.57.07.png

动画

Canvas 视图不支持动画,但是你可以用动画调度器把它嵌入到 TimelineView中。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
less复制代码struct ContentView: View {
var body: some View {
TimelineView(.animation) { timelineContext in
let value = secondsValue(for: timelineContext.date)

Canvas(
opaque: true,
colorMode: .linear,
rendersAsynchronously: false
) { context, size in
let newSize = size.applying(.init(scaleX: value, y: 1))
let rect = CGRect(origin: .zero, size: newSize)

context.fill(
Rectangle().path(in: rect),
with: .color(.purple)
)
}
}
}

private func secondsValue(for date: Date) -> Double {
let seconds = Calendar.current.component(.second, from: date)
return Double(seconds) / 60
}
}

该代码的实际效果大家可以自行在 SwiftUI 中的预览中自行查看。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%