起因
最近想把之前做的微信小程序"花瓣单词挑战"搬到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)
}
}
下一步:组成完整的花
有了一个花瓣,想要组成整朵花就简单了:
- 复制几个花瓣
- 用
rotationEffect
转个角度 - 用
offset
调调位置 - 拼成一朵完整的花
踩坑总结
这次折腾下来,几个心得:
- SwiftUI坐标系真的很反直觉,特别是Y轴向下和角度定义
- addQuadCurve比
addArc
好用多了,画自然曲线必备 - 控制点很关键,多试试不同位置就能找到想要的效果
- 多在Canvas里实时预览,改代码立马能看到效果
最后
虽然过程有点曲折,但最后还是搞定了花瓣形状。现在可以继续做我的"花瓣单词挑战"iOS版了,争取早点上线。
写这篇文章主要是记录一下踩坑过程,虽然写得不是特别清楚,但至少愿意记录了,也算是进步吧。
如果你也在搞SwiftUI自定义形状,希望这篇文章能帮到你。有问题的话欢迎一起讨论。