前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SwiftUI:使用 CGAffineTransform 和奇偶填充来变换形状

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

作者头像
韦弦zhy
发布2020-05-08 00:58:32
1.4K0
发布2020-05-08 00:58:32
举报
\color{red}{\Large \mathbf{Hacking \quad with \quad iOS: SwiftUI \quad Edition}}
\color{red}{\Large \mathbf{Hacking \quad with \quad iOS: SwiftUI \quad Edition}}
{\Large \mathbf{Drawing:Transforming}}
{\Large \mathbf{Drawing:Transforming}}

当您不再满足于简单的形状和路径时,SwiftUI的两个有用功能会合在一起,以极少的工作量创建出漂亮的效果。第一个是CGAffineTransform,它描述了如何旋转,缩放或剪切路径或视图。第二个是奇偶填充(even-odd fills),它使我们可以控制应如何渲染重叠的形状。

为了演示这两种方法,我们将用几个旋转的椭圆形花瓣创建一个花朵形状,每个椭圆形都围绕一个圆放置。这背后的数学方法相对简单,只有一个需要注意点:CGAffineTransform以弧度而非角度来度量角度。如果您上学已经有一段时间了,那么您至少需要知道的是:3.141弧度等于180度,所以3.141弧度乘以2等于360度。3.141并非巧合:实际值是数学常数 π

因此,我们要做的事情:

  • 创建一个新的空路径。
  • 从0到π乘以2(弧度为360度),然后每次计数为π的八分之一,这将为我们提供16个花瓣。
  • 创建一个等于当前数字的旋转变换。
  • 旋转变换的移动量等于绘制空间宽度和高度的一半,因此每个花瓣都以我们的形状为中心。
  • 为花瓣创建一个新路径,该路径等于特定大小的椭圆。
  • 将变换应用到该椭圆,以便将其移到适当位置。
  • 将花瓣的路径添加到我们的主路径中。

一旦您看到代码正在运行,这将更有意义,但是首先我想再添加三个小东西:

  1. 旋转然后移动的东西不会产生与移动然后旋转的结果相同的结果,因为先旋转时,它的移动方向将与未旋转时的不同。
  2. 为了真正帮助您了解发生了什么,我们将使花瓣椭圆使用一些可以从外部传递的属性。
  3. 如果您想一次通过数字计数,则范围为1 ... 5很好,但是如果您想以2s进行计数,或者在我们的情况下以“ pi / 8”为单位,则应使用stride(from:to:by :)代替。

好了,足够多的讨论,现在将此形状添加到您的项目中:

代码语言:swift
复制
struct Flower: Shape {
    // 花瓣移离中心多少距离
    var petalOffset: Double = -20

    // 每片花瓣的宽度
    var petalWidth: Double = 100

    func path(in rect: CGRect) -> Path {
        // 容纳所有花瓣的路径
        var path = Path()

        // 从0向上计数到 pi * 2,每次移动 pi / 8
        for number in stride(from: 0, to: CGFloat.pi * 2, by: CGFloat.pi / 8) {
            // 根据循环旋转当前的花瓣
            let rotation = CGAffineTransform(rotationAngle: number)

            // 将花瓣移到我们视野的中心
            let position = rotation.concatenating(CGAffineTransform(translationX: rect.width / 2, y: rect.height / 2))

            // 使用我们的属性以及固定的Y和高度为该花瓣创建路径
            let originalPetal = Path(ellipseIn: CGRect(x: CGFloat(petalOffset), y: 0, width: CGFloat(petalWidth), height: rect.width / 2))

            // 将我们的旋转/位置变换应用于花瓣
            let rotatedPetal = originalPetal.applying(position)

            // 将其添加到我们的主路径
            path.addPath(rotatedPetal)
        }

        // 现在将主径 return
        return path
    }
}

我意识到有很多代码,但是希望当您试用时,它会变得更加清晰。将您的ContentView修改为下方代码:

代码语言:swift
复制
struct ContentView: View {
    @State private var petalOffset = -20.0
    @State private var petalWidth = 100.0

    var body: some View {
        VStack {
            Flower(petalOffset: petalOffset, petalWidth: petalWidth)
                .stroke(Color.red, lineWidth: 1)

            Text("Offset")
            Slider(value: $petalOffset, in: -40...40)
                .padding([.horizontal, .bottom])

            Text("Width")
            Slider(value: $petalWidth, in: 0...100)
                .padding(.horizontal)
        }
    }
}

现在尝试一下。一旦开始拖动offset和width滑块,您应该就能清楚地看到代码的工作原理——它只是一系列旋转的椭圆,呈圆形排列。

stroke flower
stroke flower

这本身就是有趣的,但是只要稍作改动,我们就可以从有趣升华。如果您查看绘制椭圆的方式,它们经常重叠——有时一个椭圆绘制在另一个椭圆上,有时绘制在其他多个椭圆上。

如果我们使用纯色填充路径,则会得到相当不令人印象深刻的结果。像这样尝试:

代码语言:swift
复制
Flower(petalOffset: petalOffset, petalWidth: petalWidth)
    .fill(Color.red)
fill flower
fill flower

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

  • 如果路径没有重叠,它将被填充。
  • 如果另一条路径重叠,则重叠的部分将不会被填充。
  • 如果第三个路径与前两个路径重叠,则会被填充。
  • …等等。

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

尝试一下:

代码语言:swift
复制
Flower(petalOffset: petalOffset, petalWidth: petalWidth)
    .fill(Color.red, style: FillStyle(eoFill: true))
FillStyle(eoFill: true)
FillStyle(eoFill: true)

现在运行程序并开始播放——坦白地说,鉴于我们所做的工作很少,结果非常吸引人!

译自Transforming shapes using CGAffineTransform and even-odd fills

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档