SwiftUI画花瓣踩坑记:从小白到入门的Shape Protocol实战

详细记录使用SwiftUI Shape Protocol绘制自定义花瓣形状的完整过程,包括addQuadCurve、坐标系统等实用技巧

起因

最近想把之前做的微信小程序"花瓣单词挑战"搬到iOS上。这个小程序就是在花瓣上点字母组词的游戏,在小程序里用CSS画花瓣很简单,但换到SwiftUI就头疼了。

我这个SwiftUI半吊子,之前也就做过几个没啥技术含量的小APP,画个花瓣都不知道从哪下手。Google搜了一圈"SwiftUI 花瓣",找到了这篇文章,虽然不是我想要的样子,但至少有个思路了。

Shape Protocol是个啥?

简单说,SwiftUI的Shape Protocol就是让你自己画形状的工具。你只要实现一个path(in rect: CGRect) -> Path方法,告诉它怎么画就行了。

第一次尝试:用addArc画弧形

参考文章里用的是addArc方法:

struct DaisyPental: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: rect.minX, y: rect.midY))
        path.addArc(
            center: CGPoint(x: rect.maxX, y: rect.midY), 
            radius: rect.maxX*(1/11),
            startAngle: .degrees(90),
            endAngle: .degrees(270),
            clockwise: true
        )
        return path
    }
}

为了搞清楚这代码干啥的,我在Xcode里试了试,还加了个红色背景方便看:

struct ShapTest: View {
    var body: some View {
        DaisyPental()
            .fill(.yellow.gradient)
            .frame(width: 110, height: 20)
            .background(.red)  // 红色背景看得清楚
    }
}

SwiftUI的坐标系有点反常识

这里有个坑,SwiftUI的坐标系和我们平时想的不太一样:

  • 原点在左上角,不是左下角
  • X轴:往右是正方向(这个还算正常)
  • Y轴:往下是正方向(这就有点反直觉了)

所以:

  • rect.minX, rect.minY → 左上角
  • rect.maxX, rect.maxY → 右下角
  • rect.midX, rect.midY → 正中间

你可以在Xcode开着Canvas预览,然后改改path.move里的x、y值看看效果。

addArc的角度更反常识

addArc画圆弧的角度定义也挺奇葩的:

  • 0° → 最右边
  • 90° → 下面(注意不是上面!)
  • 180° → 最左边
  • 270° → 上面

这和我们平时学的数学坐标系完全不一样,被坑了好久。

addQuadCurve:画曲线的神器

后来发现用addArc画不出我想要的花瓣形状,在YouTube上看到有人用addQuadCurve画曲线,试了试发现这个更好用。

addQuadCurve的原理很简单:

path.addQuadCurve(
    to: CGPoint,      // 终点
    control: CGPoint  // 控制点
)
  • 起点:就是画笔现在的位置
  • 终点:你想画到哪里
  • 控制点:这个点决定曲线怎么弯,调整这个点就能得到不同的弯曲效果

具体的数学原理我也搞不太清楚,反正就是调控制点的位置,曲线就会跟着变。

我的花瓣最终版本

经过各种试验,最后搞出了这个花瓣:

import SwiftUI

struct PetalShap: Shape {
    func path(in rect: CGRect) -> Path {
        Path { path in
            // 从这个点开始画
            path.move(to: CGPoint(x: rect.minX + 30, y: rect.minY + 30))
            
            // 画花瓣左边的曲线
            path.addQuadCurve(
                to: CGPoint(x: rect.minX + 30, y: rect.minY - 30),
                control: CGPoint(x: rect.minX - 10, y: rect.minY)
            )
            
            // 画花瓣尖尖的部分
            path.addQuadCurve(
                to: CGPoint(x: rect.maxX * 1.8, y: rect.minY),
                control: CGPoint(x: rect.midX + 40, y: -rect.maxY + 30)
            )
            
            // 画花瓣右边,回到起始点
            path.addQuadCurve(
                to: CGPoint(x: rect.minX + 30, y: rect.minY + 30),
                control: CGPoint(x: rect.midX + 40, y: rect.maxY - 30)
            )
        }
    }
}

用起来就是这样:

struct ShapTest: View {
    var body: some View {
        PetalShap()
            .fill(.yellow.gradient)
            .frame(width: 110, height: 110)
    }
}

下一步:组成完整的花

有了一个花瓣,想要组成整朵花就简单了:

  1. 复制几个花瓣
  2. rotationEffect转个角度
  3. offset调调位置
  4. 拼成一朵完整的花

踩坑总结

这次折腾下来,几个心得:

  • SwiftUI坐标系真的很反直觉,特别是Y轴向下和角度定义
  • addQuadCurveaddArc好用多了,画自然曲线必备
  • 控制点很关键,多试试不同位置就能找到想要的效果
  • 多在Canvas里实时预览,改代码立马能看到效果

最后

虽然过程有点曲折,但最后还是搞定了花瓣形状。现在可以继续做我的"花瓣单词挑战"iOS版了,争取早点上线。

写这篇文章主要是记录一下踩坑过程,虽然写得不是特别清楚,但至少愿意记录了,也算是进步吧。

如果你也在搞SwiftUI自定义形状,希望这篇文章能帮到你。有问题的话欢迎一起讨论。

参考链接