Clocks run backwards in a SwiftUI world (aka how to draw an arc with SwiftUI)
Posted: | Author: Jörn | Filed under: iOS, Swift, SwiftUI | Tags: iOS, SwiftUI | No Comments »Sometimes you have to draw something on the screen that is not a full circle but only a part of the circle. Something like this:

The obvious way to do this would be to create a custom Arc Shape like this:
struct Arc: Shape {
var startAngle: Angle
var endAngle: Angle
var clockwise: Bool
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.width / 2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: clockwise)
return path
}
}
Now we need to let SwiftUI know what kind of arc we want to draw. As a human I would describe the arc as starting at 90 degrees and ending at 180 degrees, because we are usually assuming that 0 degrees is at the top of a circle.
SwiftUI begs to differ. In SwiftUI 0 degrees means straight to the right. In other words:
Human 90 degrees == SwiftUI 0 degrees
Because of that we need to draw and arc from 0 degrees to 90 degrees. (I add a gray background, so we can see the frame of the arc)
struct ContentView: View {
var body: some View {
Arc(startAngle: .degrees(0),
endAngle: .degrees(90),
clockwise: true
)
.stroke(Color("pdMagenta"), lineWidth: 12)
.frame(width: 200, height: 200)
}
}
Seems pretty straightforward, but the result is a not what I would expect:

This is where things get wild. According to Apple’s docs SwiftUI (and UIKit) use a flipped coordinate system.
To explain what that means let’s have look at these two illustrations:

In a Cartesian coordinate system (that we humans are used to) increasing values on the Y-axis go up to the top. Resulting in the “clockwise” that we are used to.

In SwiftUI’s flipped coordinate system increasing values on the Y-axis are going down to the bottom of the canvas. And that results in a reversed “clockwise” direction. IMHO that is really counterintuitive, but that’s the way it is 🤷♂️
This is an excerpt from the Apple docs:
“In a flipped coordinate system … specifying a clockwise arc results in a counterclockwise arc”
In other words: Clocks run backwards in a SwiftUI context.
To get the arc we want we have to toggle the clockwise parameter in our custom ArcShape:
struct Arc: Shape {
var startAngle: Angle
var endAngle: Angle
var clockwise: Bool
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.width / 2,
startAngle: startAngle,
endAngle: endAngle,
clockwise: !clockwise)
return path
}
}
And now we get want we wanted in the first place:
