I'm trying to create a circular achievement view similar to the one seen in Apple Game Center using SwiftUI. The view should have circular text at the top and a centered arc wrapper text.
Here's what I'm aiming to achieve:
Circular Text: Text arranged in a circular manner at the top of the view. Arc Title: A title that wraps around an arc, centered within the circular view. I've tried using ZStack and Text views with rotation, but I'm having trouble getting the text to align correctly in a circular path. Additionally, I'm not sure how to position the arc title properly.
A SwiftUI solution for curved text can be found in the answer to SwiftUI: How to have equal spacing between letters in a curved text view? (it was my answer). Actually, it looks like you found this post already because your own answer here seems to be based on the code in that question (or the follow-up questions from the same OP). But you're not using CurvedText
in your answer.
The remainder of the badge can be built up with a ZStack
.
To create the gap in the circle where the title is shown, use .trim
to shorten the path, then .rotationEffect
to move the gap into the 12 o' clock position.
The size of the gap is the only slightly tricky part. If you don't like using a fixed arc fraction, you could try using an approximation of the angle based on the size of the label, or you need to get the actual arc angle of the text.
The arc angle is known inside the view CurvedText
, so you might want to consider adapting that view. See How to determine the angle of the first character in a curved text view in SwiftUI? for an example of where a similar adaption is being used.
Finally, the curved text can be added to the ZStack
as an overlay. This way, it doesn't impact the size of the ZStack
.
ZStack {
Circle()
.trim(from: 0.1, to: 0.9)
.stroke(style: .init(lineWidth: 6, lineCap: .round))
.rotationEffect(.degrees(-90))
.padding(10)
Image(.image3)
.resizable()
.scaledToFill()
.clipShape(.circle)
.padding(40)
Circle()
.stroke(lineWidth: 6)
.padding(40)
}
.frame(width: 300, height: 300)
.overlay(alignment: .top) {
// See https://stackoverflow.com/a/77280669/20386264
CurvedText(string: "March 4 2025", radius: 140)
.font(.title3)
.fontWeight(.medium)
}