The Origin
Recently, I wanted to port my WeChat Mini Program "Petal Word Challenge" to iOS. This mini-program is a game where you tap letters on petals to form words. Drawing petals with CSS in the mini-program was straightforward, but switching to SwiftUI gave me a headache.
As someone with half-baked SwiftUI skills, having only built a few technically simple apps before, I had no idea where to start drawing a petal. After googling "SwiftUI petal," I found this article. While it wasn't exactly what I envisioned, it at least gave me a starting point.
What is the Shape Protocol?
Simply put, SwiftUI's Shape Protocol is a tool for creating custom shapes. You just need to implement a path(in rect: CGRect) -> Path
method to define how the shape should be drawn.
First Attempt: Drawing Arcs with addArc
The referenced article used the addArc
method:
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
}
}
To understand what this code did, I tried it in Xcode, adding a red background for clarity:
struct ShapTest: View {
var body: some View {
DaisyPental()
.fill(.yellow.gradient)
.frame(width: 110, height: 20)
.background(.red) // Red background for visibility
}
}
SwiftUI's Coordinate System is Counterintuitive
Here's a gotcha: SwiftUI's coordinate system doesn't work like you might expect:
- Origin at Top-Left: Not bottom-left.
- X-axis: Positive to the right (this is normal).
- Y-axis: Positive downwards (this is counterintuitive!).
Therefore:
rect.minX
,rect.minY
→ Top-left cornerrect.maxX
,rect.maxY
→ Bottom-right cornerrect.midX
,rect.midY
→ Center point
You can experiment by modifying the x and y values in path.move
while watching the Xcode Canvas preview.
addArc Angles are Even More Counterintuitive
The angle definition for addArc
is particularly quirky:
- 0° → Right
- 90° → Down (Note: Not up!)
- 180° → Left
- 270° → Up
This is completely different from the standard mathematical coordinate system and tripped me up for a while.
addQuadCurve: The Curve-Drawing Power Tool
I later realized addArc
couldn't create the petal shape I wanted. Seeing someone use addQuadCurve
on YouTube, I tried it and found it much more suitable.
The principle of addQuadCurve
is simple:
path.addQuadCurve(
to: CGPoint, // End point
control: CGPoint // Control point
)
- Start Point: The current position of the drawing pen.
- End Point: Where you want the curve to end.
- Control Point: This point determines how the curve bends. Adjusting it changes the curvature effect.
I don't fully grasp the underlying math, but tweaking the control point position visibly alters the curve.
My Final Petal Implementation
After much experimentation, I arrived at this petal shape:
import SwiftUI
struct PetalShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
// Start drawing from this point
path.move(to: CGPoint(x: rect.minX + 30, y: rect.minY + 30))
// Draw left curve of the petal
path.addQuadCurve(
to: CGPoint(x: rect.minX + 30, y: rect.minY - 30),
control: CGPoint(x: rect.minX - 10, y: rect.minY)
)
// Draw the tip of the petal
path.addQuadCurve(
to: CGPoint(x: rect.maxX * 1.8, y: rect.minY),
control: CGPoint(x: rect.midX + 40, y: -rect.maxY + 30)
)
// Draw the right side of the petal, returning to start
path.addQuadCurve(
to: CGPoint(x: rect.minX + 30, y: rect.minY + 30),
control: CGPoint(x: rect.midX + 40, y: rect.maxY - 30)
)
}
}
}
Usage example:
struct ShapeTest: View {
var body: some View {
PetalShape()
.fill(.yellow.gradient)
.frame(width: 110, height: 110)
}
}
Next Step: Composing the Full Flower
With one petal shape ready, assembling a full flower becomes straightforward:
- Duplicate several petals.
- Rotate each using
rotationEffect
. - Adjust positions with
offset
. - Combine them into a complete flower.
Lessons Learned
Key takeaways from this process:
- SwiftUI's Coordinate System: Is genuinely counterintuitive, especially the downward Y-axis and angle definitions.
- addQuadCurve > addArc: Far superior for drawing natural curves.
- Control Point is Key: Experimenting with its position is crucial for the desired effect.
- Live Preview is Essential: Use the Canvas preview constantly to see changes immediately.
Finally
Despite the twists and turns, I successfully created the petal shape. I can now continue developing the iOS version of "Petal Word Challenge" and aim for a release soon.
I wrote this post primarily to document the pitfalls encountered. While it might not be perfectly clear, the willingness to document it is progress in itself.
If you're also working on custom SwiftUI shapes, I hope this helps. Feel free to discuss any questions!