How to add round tip on trim circle in SwiftUI
I want make progress bar (circle) with big round tip, that will follow to progress. Can you please suggest me, Is there any standard way to do it? Or it requires additional circle and some calculation to understand position?
struct Loader: View {
var percent: CGFloat = 0
var colors: [Color] = [
Color(red: 0.949, green: 0.9098, blue: 0.8706),
Color(red: 0.9569, green: 0.5882, blue: 0.2196)
]
var body: some View {
ZStack{
Circle()
.fill(Color.white)
.frame(width: 300, height: 300, alignment: .center)
.overlay(
Circle()
.trim(from: 0, to: percent * 0.01)
.stroke(style: StrokeStyle(lineWidth: 22, lineCap: .butt, lineJoin: .miter, miterLimit: 10))
.fill(AngularGradient(gradient: .init(colors: colors), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)))
.rotationEffect(.degrees(-90))
).animation(.spring(response: 1.0, dampingFraction: 1.0, blendDuration: 1.0))
Text(String(format: "%.1f", percent) + " %").font(.system(size: 20)).fontWeight(.heavy)
}
}
}
What i want to make:
What i have done already:
Here's potential solution. I added an overlay on top of the Loader, I made it's height the full size of the big Circle, and then simply rotated the overlay with the completion. This way they animate at the same time.
import SwiftUI
struct CircleView: View {
@State var percent: CGFloat = 0
var body: some View {
Loader(percent: $percent)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
withAnimation(Animation.linear(duration: 1.0)) {
percent = 20
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
withAnimation(Animation.linear(duration: 2.0)) {
percent = 55
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
withAnimation(Animation.linear(duration: 4.0)) {
percent = 100
}
}
}
}
}
struct CircleView_Previews: PreviewProvider {
static var previews: some View {
CircleView()
}
}
struct Loader: View {
@Binding var percent: CGFloat
var colors: [Color] = [
Color(red: 0.949, green: 0.9098, blue: 0.8706),
Color(red: 0.9569, green: 0.5882, blue: 0.2196)
]
let circleHeight: CGFloat = 300
var body: some View {
let pinHeight = circleHeight * 0.1
let completion = percent * 0.01
Circle()
.trim(from: 0, to: completion)
.stroke(style: StrokeStyle(lineWidth: 22, lineCap: .butt, lineJoin: .miter, miterLimit: 10))
.fill(AngularGradient(gradient: .init(colors: colors), center: .center, startAngle: .zero, endAngle: .init(degrees: 360)))
.rotationEffect(.degrees(-90))
.frame(width: circleHeight, height: circleHeight)
.overlay(
Text(String(format: "%.1f", percent) + " %").font(.system(size: 20)).fontWeight(.heavy)
.animation(nil)
)
.overlay(
Circle()
.frame(width: pinHeight, height: pinHeight)
.offset(y: -pinHeight / 2)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.rotationEffect(Angle(degrees: 360 * Double(completion)))
)
}
}