[SOLVED] SwiftUI: How to create circular "progress" bar widget with a "fade out" start line if it is full (100%), like the "battery" widget on iOS

Issue

I am creating my own "circular" progress bar widget in SwiftUI and I am wondering how to approach implementing he following. If the circle progress is at 100% (meaning the circle if full) I want the "start" of the line to have a "faded out" effect, so that the start and end of the line do not "merge". I saw this in the "battery" widgets on iOS. Here is a an example of "full" and "not full" battery widget:

enter image description here
enter image description here

Here is my current widget code:

struct CircleProgressView: View {
    
    @Binding var progress: Float
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(lineWidth: 10)
                .opacity(0.3)
                .foregroundColor(Color(UIColor.systemGray3))
            
            Circle()
                .trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
                .stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
                .foregroundColor(Color(UIColor.systemGreen))
                .rotationEffect(Angle(degrees: 270.0))
        }
        .padding()
    }
}

Any ideas will be helpful.

Solution

Here is an approach.
I stacked 4 circles:

  • the grey base
  • one first green circle to receive the shadow
  • one circle clipped to a dot with shadow + clipshape to get rid of shadows on the sides of the circle
  • another green circle to cover the shadow on one side

Here is the code:

struct CircleProgressView: View {
    
    @Binding var progress: Float
    
    var body: some View {
        ZStack {
            // grey background circle
            Circle()
                .stroke(lineWidth: 30)
                .opacity(0.3)
                .foregroundColor(Color(UIColor.systemGray3))

            // green base circle to receive shadow
            Circle()
                .trim(from: 0.0, to: CGFloat(min(self.progress, 0.5)))
                .stroke(style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
                .foregroundColor(Color(UIColor.systemGreen))
                .rotationEffect(Angle(degrees: 270.0))

            // point with shadow, clipped
            Circle()
                .trim(from: CGFloat(abs((min(progress, 1.0))-0.001)), to: CGFloat(abs((min(progress, 1.0))-0.0005)))
                .stroke(style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
                .foregroundColor(Color(UIColor.blue))
                .shadow(color: .black, radius: 10, x: 0, y: 0)
                .rotationEffect(Angle(degrees: 270.0))
                .clipShape(
                    Circle().stroke(lineWidth: 30)
                )
            
            // green overlay circle to hide shadow on one side
            Circle()
                .trim(from: progress > 0.5 ? 0.25 : 0, to: CGFloat(min(self.progress, 1.0)))
                .stroke(style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
                .foregroundColor(Color(UIColor.systemGreen))
                .rotationEffect(Angle(degrees: 270.0))


            
        }
        .padding()
    }
}

enter image description here

Answered By – ChrisR

Answer Checked By – Marilyn (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published.