Felix Marianayagam
Felix Marianayagam

Reputation: 2714

Using SwiftUI, how do I animate pie progress bar

I've created the code for displaying the pie progress bar. I need to add an animation to this progress bar. I tried linear animation but it didn't help. I'm stuck and don't know how to get it to animate. Can someone help? Here's the code.

import SwiftUI

struct PieProgress: View {
    @State var progress: Float

    var body: some View {
        GeometryReader { geometry in
            VStack(spacing:20) {
                HStack{
                    Text("0%")
                    Slider(value: self.$progress)
                    Text("100%")
                }.padding()
                Circle()
                    .stroke(Color.gray, lineWidth: 1)
                    .frame(width: geometry.size.width, height: geometry.size.width, alignment: .center)
                    .padding()
                    .overlay(
                        PieShape(progress: Double(self.progress))
                            .frame(width: geometry.size.width - 10, height: geometry.size.width - 10 , alignment: .center)
                            .foregroundColor(.blue)
                    )
            }
        }
    }
}

struct PieShape: Shape {
    var progress: Double = 0.0
    private let startAngle: Double = (Double.pi) * 1.5
    private var endAngle: Double {
        get {
            return self.startAngle + Double.pi * 2 * self.progress
        }
    }

    func path(in rect: CGRect) -> Path {
        var path = Path()
        let arcCenter =  CGPoint(x: rect.size.width / 2, y: rect.size.width / 2)
        let radius = rect.size.width / 2
        path.move(to: arcCenter)
        path.addArc(center: arcCenter, radius: radius, startAngle: Angle(radians: startAngle), endAngle: Angle(radians: endAngle), clockwise: false)
        path.closeSubpath()
        return path
    }
}

Upvotes: 4

Views: 2974

Answers (1)

Asperi
Asperi

Reputation: 257779

Xcode 13.4 / iOS 15.5

With updated deprecated methods and now w/o hard-codes

demo

Circle()
    .stroke(Color.gray, lineWidth: 1)
    .overlay(
        PieShape(progress: Double(self.progress))
            .padding(4)
            .foregroundColor(.blue)
    )
    .frame(maxWidth: .infinity)
    .animation(Animation.linear, value: progress) // << here !!
    .aspectRatio(contentMode: .fit)

Test code is here

Original

Here is possible approach (tested & works with Xcode 11.2 / iOS 13.2):

  1. You need to specify animatableData for your shape as below

    struct PieShape: Shape { var progress: Double = 0.0

     var animatableData: Double {
         get {
             self.progress
         }
         set {
             self.progress = newValue
         }
     }
     ...
    
  2. then add animation to Circle

    Circle() .stroke(Color.gray, lineWidth: 1) .frame(width: geometry.size.width, height: geometry.size.width, alignment: .center) .padding() .overlay( PieShape(progress: Double(self.progress)) .frame(width: geometry.size.width - 10, height: geometry.size.width - 10 , alignment: .center) .foregroundColor(.blue) ) .animation(Animation.linear) // << here !!

and that's it. For testing (only!) you can add the following in PieProgress

.onAppear {
    DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
        self.progress = 0.72
    }
}

Note: to use PieProgress as reused component it would be reasonable to make progress as @Binding, just in case.

Upvotes: 3

Related Questions