欢迎光临散文网 会员登陆 & 注册

SwiftUI学习100天(Day44 - 项目 9,第二部分)

2023-02-16 12:00 作者:爱上树の蜗牛  | 我要投稿

原创链接:https://www.hackingwithswift.com/100/swiftui

以下内容仅供学习参考:

今天,我们将通过一点点创意继续我们对 SwiftUI 绘图系统的了解——我想你会惊讶地发现,只需将你所知道的大部分知识与一些新技术相结合,就可以如此轻松地制作出令人着迷的东西。

今天,你还将遇到drawingGroup()修改器,它让我们可以将视图渲染结合到由 Apple 的高性能图形 API Metal 提供支持的单一过程中。过去很多人问我是否打算写一本关于 Metal 的书,答案是否定的——不仅已经有一本非常好的书了,而且从 API 中获得任何好的东西也非常困难.

那不是因为 Metal 不好——相信我,它太棒了!– 而是因为 Apple 最优秀的工程师在使用 Metal 时努力使 SwiftUI 尽可能高效,而且,坦率地说,我不太可能做得更好。

你会发现,切换 Metal 并不是你经常需要的事情,尽管这很容易做到。著名的软件开发人员 Kent Beck 曾经说过,我们的流程应该是“让它工作、让它正确、让它快速”,他说得很对。但是,如果你发现你的绘图在不切换到 Metal 的情况下工作速度很快,那么通常最好保持原样。

不管怎样,聊够了——我说我们会做一些令人着迷的事情,所以让我们开始吧!

今天,你要完成三个主题,在这些主题中你将了解CGAffineTransformImagePaintdrawingGroup()等等。

使用 CGAffineTransform 和奇偶填充变换形状

当你超越简单的形状和路径时,SwiftUI 的两个有用功能将结合在一起,以非常少的工作创建一些美丽的效果。第一个是CGAffineTransform,它描述了路径或视图应该如何旋转、缩放或剪切;第二个是(even-odd fills)奇偶填充,它允许我们控制重叠形状的渲染方式。

为了演示这两者,我们将用几个旋转的椭圆花瓣创建一个花的形状,每个椭圆围绕一个圆定位。这背后的数学原理相对简单,有一个问题:CGAffineTransform用弧度而不是度数来测量角度。如果你上学已经有一段时间了,你至少需要知道的是:3.141 弧度等于 180 度,所以 3.141 弧度乘以 2 等于 360 度。3.141 并非巧合:实际值是数学常数 pi。

所以,我们要做的是:

  • 创建一个新的空路径。

  • 从 0 数到 pi 乘以 2(以弧度表示的 360 度),每次以 pi 的八分之一计数——这将给我们 16 个花瓣。

  • 创建等于当前数字的旋转变换。

  • 添加到该旋转等于我们绘制空间宽度和高度的一半的运动,因此每个花瓣都以我们的形状为中心。

  • 为花瓣创建一条新路径,等于特定大小的椭圆。

  • 将我们的变换应用于该椭圆,使其移动到位。

  • 将该花瓣的路径添加到我们的主路径中。

一旦你看到代码正在运行,这将更有意义,但首先我想添加三件小事:

  1. 旋转然后移动它不会产生与移动然后旋转相同的结果,因为当你先旋转它时它移动的方向将与它不旋转时不同。

  2. 为了真正帮助你了解正在发生的事情,我们将使我们的花瓣椭圆使用一些我们可以从外部传入的属性。

  3. 如果你想一次数一个数,那么范围1...5非常好,但是如果你想以 2 秒为单位进行计数,或者在我们的例子中以“pi/8”为单位进行计数,则应该改用stride(from:to:by:)

好了,废话不多说了——现在将这个形状添加到你的项目中:

我意识到这是相当多的代码,但希望当你尝试它时它会变得更加清晰。将你的ContentView修改为:

现在试试看。一旦开始拖动偏移量和宽度滑块,你应该能够准确地看到代码是如何工作的——它只是一系列旋转的椭圆,以圆形形式放置。

这本身就很有趣,但是通过一个小的改变,我们可以从有趣变成崇高。如果你看一下我们画椭圆的方式,它们经常重叠——有时一个椭圆画在另一个椭圆上,有时画在其他几个椭圆上。

如果我们使用纯色填充我们的路径,我们会得到一个相当不起眼的结果。试试这样:

但作为替代方案,我们可以使用奇偶规则填充形状,该规则决定是否应根据路径包含的重叠部分对路径的一部分进行着色。它是这样工作的:

  • 如果路径没有重叠,它将被填充。

  • 如果另一条路径与其重叠,则重叠部分将不会被填充。

  • 如果第三条路径与前两条路径重叠,则它将被填充。

  • …等等。

只有实际重叠的部分受此规则影响,它会产生一些非常漂亮的结果。更好的是,Swift UI 使它的使用变得简单,因为每当我们一个形状上调用fill()时,我们都可以传递一个FillStyle结构,要求启用奇偶规则。

试试这个:

现在运行程序并开始游戏——老实说,考虑到我们所做的工作如此之少,结果非常令人着迷!


使用 ImagePaint 的创意边框和填充

SwiftUI 严重依赖协议,这在处理绘图时可能会有点混乱。例如,我们可以用Color作视图,但它也符合ShapeStyle - 用于填充、描边和边框的不同协议。

实际上,这意味着我们可以修改默认文本视图,使其具有红色背景:

或红色边框:

相反,我们可以使用图像作为背景:

但是使用相同的图像作为边框是行不通的:

如果你考虑一下,这是有道理的——除非图像的尺寸完全正确,否则你几乎无法控制它的外观。

为了解决这个问题,SwiftUI 为我们提供了一种专用类型,它以一种我们可以完全控制图像呈现方式的方式包装图像,这反过来意味着我们可以毫无问题地将它们用于边框和填充。

该类型称为ImagePaint,它是使用一到三个参数创建的。至少你需要给它一个Image作为它的第一个参数的工作,但你也可以在该图像中提供一个矩形以用作在 0 到 1 范围内指定的绘图源(第二个参数),以及该图像的比例(第三个参数)。第二个和第三个参数具有合理的默认值“整个图像”和“100% 比例”,因此你有时可以忽略它们。

例如,我们可以使用 0.2 的比例渲染示例图像,这意味着它以正常尺寸的 1/5 显示:

如果你想尝试使用sourceRect参数,请确保你传入了CGRect相对大小和位置的参数:0 表示“开始”,1 表示“结束”。例如,这将显示示例图像的整个宽度,但只显示中间的一半:

值得添加的是,ImagePaint可用于视图背景和形状笔触。例如,我们可以创建一个胶囊,我们的示例图像平铺为其笔画:

ImagePaint将自动继续平铺其图像,直到填满其区域——它可以处理任何大小的背景、笔触、边框和填充。

使用 drawingGroup() 启用高性能 Metal 渲染

SwiftUI 默认使用 Core Animation 进行渲染,它提供了开箱即用的出色性能。然而,对于复杂的渲染,你可能会发现你的代码开始变慢——任何低于每秒 60 帧 (FPS) 的东西都是一个问题,但实际上你应该瞄准更高的目标,因为许多 iOS 设备现在以 120fps 渲染。

为了证明这一点,让我们看一些示例代码。我们将创建一个颜色循环视图,以一系列颜色呈现同心圆。结果看起来像径向渐变,但我们将添加两个属性以使其更具可定制性:一个用于控制应绘制多少个圆圈,另一个用于控制颜色循环——它将能够移动渐变开始和结束颜色。

我们可以通过使用Color(hue:saturation:brightness:)初始化器获得颜色循环效果:色调是一个从 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 的范围内。

这是代码:

我们现在可以在布局中使用它,将其颜色循环绑定到由滑块控制的本地属性:

如果你运行该应用程序,你会看到我们有一个整洁的彩色波浪效果,完全通过在滑块周围拖动来控制,而且效果非常流畅。

你现在看到的是由 Core Animation 提供支持的,这意味着它将把我们的 100 个圆圈变成 100 个单独的视图绘制到屏幕上。这在计算上是昂贵的,但正如你所见,它运行良好——我们获得了平稳的性能。

然而,如果我们稍微增加复杂性,我们会发现事情并不那么乐观。用这个strokeBorder()替换现有的修改器:

现在呈现出柔和的渐变,在圆圈顶部显示明亮的颜色,在底部显示较暗的颜色。现在,当你运行该应用程序时,你会发现它的运行速度要慢得多——SwiftUI 正在努力渲染 100 个渐变作为 100 个独立视图的一部分。

我们可以通过应用一个名为drawingGroup(). 这告诉 SwiftUI 它应该将视图的内容渲染到屏幕外图像中,然后再将其作为单个渲染输出放回到屏幕上,这样速度要快得多。在幕后,这是由 Metal 提供支持的,Metal 是 Apple 的框架,用于直接与 GPU 一起工作以获得极快的图形。

因此,将ColorCyclingCircle正文修改为:

现在再次运行它 - 有了这个微小的添加,您现在会发现我们得到了正确渲染的所有内容,并且即使使用渐变,我们也可以全速返回。

重要提示:了解修饰符并保存在你的drawingGroup()武器库中有助于解决性能问题,但你不应该经常使用它。添加离屏渲染通道可能会降低 SwiftUI 的简单绘图速度,因此你应该等到遇到实际性能问题后再尝试引入drawingGroup().


SwiftUI学习100天(Day44 - 项目 9,第二部分)的评论 (共 条)

分享到微博请遵守国家法律