0

I'm trying to implement a circular progress bar in SwiftUI with an image on the progress and animation. Any help or idea would be appreciated.

Here is what I plan to implement:imageLink

This is what I could implement so far: image2

And this is my code:

struct CircularView: View {
    @State var progress:Double = 0
    var total:Double =  100
    let circleHeight:CGFloat = 217
    @State var xPos:CGFloat = 0.0
        
    @State var yPos:CGFloat = 0.0
    var body: some View {
        let pinHeight = circleHeight * 0.1
        
        VStack {
            
            Circle()
                .trim(from: 0.0, to: 0.6)
                .stroke(Color(.systemGray5),style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
                .frame(width: 278, height: 217, alignment: .center)
                .rotationEffect(.init(degrees: 162))
                .overlay(
                    Circle()
                        .trim(from: 0.0, to: CGFloat(progress) / CGFloat(total))
                        .stroke(Color.purple,style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
                        
                        .rotationEffect(.init(degrees: 162))
                        .rotation3DEffect(
                            .init(degrees: 1),
                            axis: (x: 1.0, y: 0.0, z: 0.0)
                        )
                        .animation(.easeOut)
                        .overlay(
                            Circle()
                                
                                .frame(width: pinHeight, height: pinHeight)
                                .foregroundColor(.blue)

                                .offset(x: 107 - xPos, y: 0 + yPos)
                                .animation(.easeInOut)
                                
                                .rotationEffect(.init(degrees: 162))
                                
                        )
                )
                .foregroundColor(.red)
            
            Button(action: {
                progress += 5
                if progress >= 65 {
                    progress = 0
                }
            }, label: {
                Text("Button")
            })
            
            
        }

        
    }
}

Mohammad
  • 1
  • 5

1 Answers1

0

Without changing much of your initial code, the idea is to rotate also the image when the circular progress bar value changes

Your code would look like this:

struct CircularView: View {
    @State var progress:Double = 0
    var total:Double =  100
    let circleHeight:CGFloat = 217
    @State var xPos:CGFloat = 0.0
        
    @State var yPos:CGFloat = 0.0
    var body: some View {
        let pinHeight = circleHeight * 0.1
        
        VStack {
            
            Circle()
                .trim(from: 0.0, to: 0.6)
                .stroke(Color(.systemGray5),style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
                .frame(width: 278, height: 217, alignment: .center)
                .rotationEffect(.init(degrees: 162))
                .overlay(
                    Circle()
                        .trim(from: 0.0, to: CGFloat(progress) / CGFloat(total))
                        .stroke(Color.purple,style: StrokeStyle(lineWidth: 8.0, lineCap: .round, dash: [0.1]))
                        
                        .rotationEffect(.init(degrees: 162))
                        .rotation3DEffect(
                            .init(degrees: 1),
                            axis: (x: 1.0, y: 0.0, z: 0.0)
                        )
//                        .animation(.easeOut)
                        .overlay(
                            Circle()
                                
                                .frame(width: pinHeight, height: pinHeight)
                                .foregroundColor(.blue)

                                .offset(x: 107 - xPos, y: 0 + yPos)
//                                .animation(.easeInOut)
                                .rotationEffect(Angle(degrees: progress * 3.6))
                                .rotationEffect(.init(degrees: 162))
                                
                        )
                )
                .foregroundColor(.red)
                .animation(.easeOut) // Animation added here
            
            Button(action: {
                progress += 5
                if progress >= 65 {
                    progress = 0
                }
            }, label: {
                Text("Button")
            })
            
            
        }

        
    }
}

1. Apply one common animation to have a better transition

I Have removed .animation modifiers in 2 places and place only one on the parent Circle View to allow a smooth animation ( You can still change and adapt it to the output you desire )

2. Calculate Image Angle rotation Degree

Perform simple calculation to determine rotation Angle, how much degree should I rotate the Image view

So, basically it is: .rotationEffect(Angle(degrees: (progress / 60) * (0.6 * 360)) => .rotationEffect(Angle(degrees: progress * 3.6))

cedricbahirwe
  • 1,274
  • 4
  • 15
  • The solution worked fantastically, but I am unable where to add my image, so it would be just like the design I'm trying to implement. – Mohammad Jun 15 '21 at 11:41
  • If I add `Image` into the third `Circle` as an `overlay` with the same `.offset` and `rotationEffect` it works, however, the image rotates as the progress forwards which is wrong. – Mohammad Jun 15 '21 at 12:07