SwiftUI默认使用Core Animation进行渲染,提供了出色的开箱即用性能。但是,对于复杂的渲染,您可能会发现代码开始变慢——任何低于每秒60帧(FPS)的问题都是一个问题,但实际上您应该将目标更高,因为许多iOS设备现在以120fps的速度渲染。
为了说明这一点,让我们看一些示例代码。我们将创建一个颜色循环视图,以各种颜色呈现同心圆。结果看起来像是一个径向渐变,但我们将添加两个属性以使其更具可定制性:一个用于控制应绘制多少个圆,另一个用于控制颜色循环——它将能够移动渐变周围的开始和结束颜色。
我们可以通过使用Color(hue:saturation:brightness:)
初始值设定项来获得颜色循环效果:hue 是从0到1的值,控制着我们所看到的颜色的种类——红色是0到1,所有其他色相都介于两者之间。为了弄清楚特定圆圈的色相,我们可以取圆圈数(例如25),除以圆圈数(例如100),然后加上颜色循环量(例如0.5)。因此,如果我们是100的第25圈,循环量为0.5,则我们的色相将为0.75。
这里的一个小复杂性是,色相在达到1.0后不会自动换行,这意味着1.0的色相等于0.0的色相,但是1.2的色相不等于0.2的色相。因此,我们将手工包裹色相:如果色相超过1.0,我们将减去1.0,以确保它始终位于0.0到1.0的范围内。
这是代码:
struct ColorCyclingCircle: View {
var amount = 0.0
var steps = 100
var body: some View {
ZStack {
ForEach(0..<steps) { value in
Circle()
.inset(by: CGFloat(value))
.strokeBorder(self.color(for: value, brightness: 1), lineWidth: 2)
}
}
}
func color(for value: Int, brightness: Double) -> Color {
var targetHue = Double(value) / Double(self.steps) + self.amount
if targetHue > 1 {
targetHue -= 1
}
return Color(hue: targetHue, saturation: 1, brightness: brightness)
}
}
现在,我们可以在布局中使用它,将其颜色循环绑定到由滑块控制的局部属性:
struct ContentView: View {
@State private var colorCycle = 0.0
var body: some View {
VStack {
ColorCyclingCircle(amount: self.colorCycle)
.frame(width: 300, height: 300)
Slider(value: $colorCycle)
}
}
}
如果您运行该应用程序,将会看到我们有一个整齐的彩色波浪效果,完全可以通过在滑块上拖动来控制,并且效果非常流畅。
简单视图.gif您现在所看到的是由Core Animation驱动的,这意味着它将把我们的100个圆变成在屏幕上绘制的100个独立视图。这在计算上是昂贵的,但是如您所见,它运行良好——我们获得了平稳的性能。
但是,如果稍微增加复杂度,我们会发现事情并不是那么乐观。用以下一个替换现有的strokeBorder()
修饰符:
.strokeBorder(LinearGradient(gradient: Gradient(colors: [
self.color(for: value, brightness: 1),
self.color(for: value, brightness: 0.5)
]), startPoint: .top, endPoint: .bottom), lineWidth: 2)
现在,这将呈现一个柔和的渐变,在圆的顶部显示明亮的颜色,在底部显示较暗的颜色。现在,当您运行该应用程序时,您会发现它运行起来要慢得多——SwiftUI正在努力为100个单独视图渲染100个渐变。
卡顿.gif
我们可以通过应用一个称为drawingGroup()
的新修改器来解决此问题。这告诉SwiftUI,在将视图内容作为单个呈现的输出放回到屏幕上之前,应将视图的内容呈现到屏幕外的图像中(离屏渲染),这要快得多。在幕后,该功能由Metal提供支持,Metal是Apple的框架,可直接与GPU协同工作以实现极快的图形。
因此,将ColorCyclingCircle
主体修改为此:
var body: some View {
ZStack {
// existing code…
}
.drawingGroup()
}
现在,再次运行它——仅需添加一点点,就可以正确渲染所有内容,即使使用渐变色,我们也可以全速返回。
Metal 渲染.gif
重要提示:drawingGroup()
修饰符有助于了解和保留您的武器库,这是解决性能问题的一种方法,但是您不应该经常使用它。添加屏幕外渲染过程可能会降低SwiftUI进行简单绘图的速度,因此,在尝试引入drawingGroup()
之前,应等到遇到实际性能问题后再进行操作。
译自 Enabling high-performance Metal rendering with drawingGroup()
网友评论