Hi I am trying to develop a semi circular interactive progress bar using swift UI but am not able to get the expected output I am trying to achieve something similar to this
Here is my code
import SwiftUIstruct SemiCircularProgressBar: View { @State var progressValue: Float = 0.0 @State private var degrees: Double = -90 var body: some View { VStack { ZStack { ProgressBar(progress: self.$progressValue) .frame(width: 250.0, height: 250.0) .padding(40.0) ProgressThumb(progress: self.$progressValue) .frame(width: 30, height: 30) .offset(x: self.thumbOffset().x, y: self.thumbOffset().y) .rotationEffect(.degrees(54.5)) .gesture( DragGesture() .onChanged { gesture in let angle = self.angle(for: gesture.location) self.updateProgress(for: angle) } ) } Spacer() } } private func angle(for location: CGPoint) -> Double { let vector = CGVector(dx: location.x, dy: location.y) let angle = atan2(vector.dy, vector.dx) return Double(angle * 180 / .pi) } private func updateProgress(for angle: Double) { let totalAngle: Double = 220 let minAngle: Double = -110 let maxAngle: Double = minAngle + totalAngle var normalizedAngle = min(max(minAngle, angle), maxAngle) if normalizedAngle > 88.8 { normalizedAngle = 88.8 } let progress = Float((normalizedAngle - minAngle) / totalAngle) self.progressValue = progress self.degrees = normalizedAngle } private func thumbOffset() -> CGPoint { let thumbRadius: CGFloat = 125 // half of progress bar diameter let radians = CGFloat(degrees) * .pi / -100 let x = thumbRadius * cos(radians) let y = thumbRadius * sin(radians) return CGPoint(x: x, y: y) }}struct ProgressBar: View { @Binding var progress: Float var body: some View { ZStack { Circle() .trim(from: 0.3, to: 0.9) .stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .round)) .opacity(0.3) .foregroundColor(Color.gray) .rotationEffect(.degrees(54.5)) Circle() .trim(from: 0.3, to: CGFloat(self.progress)) .stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .round)) .fill(AngularGradient(gradient: Gradient(stops: [ .init(color: Color(hex: "ED4D4D"), location: 0.39000002), .init(color: Color(hex: "E59148"), location: 0.48000002), .init(color: Color(hex: "EFBF39"), location: 0.5999999), .init(color: Color(hex: "EEED56"), location: 0.7199998), .init(color: Color(hex: "32E1A0"), location: 0.8099997)]), center: .center)) .rotationEffect(.degrees(54.5)) VStack { Text("824").font(Font.system(size: 44)).bold().foregroundColor(Color(hex: "314058")) Text("Great Score!").bold().foregroundColor(Color(hex: "32E1A0")) } } }}struct ProgressThumb: View { @Binding var progress: Float var body: some View { Circle() .fill(Color.blue) .frame(width: 30, height: 30) }}extension Color { init(hex: String) { let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) var int: UInt64 = 0 Scanner(string: hex).scanHexInt64(&int) let a, r, g, b: UInt64 switch hex.count { case 3: // RGB (12-bit) (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) case 6: // RGB (24-bit) (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) case 8: // ARGB (32-bit) (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) default: (a, r, g, b) = (1, 1, 1, 0) } self.init( .sRGB, red: Double(r) / 255, green: Double(g) / 255, blue: Double(b) / 255, opacity: Double(a) / 255 ) }}#Preview { SemiCircularProgressBar()}
Using this above code I am able to achieve something like this
The problems which I face here is like the thumb should be smooth and able to drag with in the semi circle. I am expecting a semicircle with a knob to show the progress. Thanks in advance