Search code examples
swiftui

SwiftUI custom ProgressIndicator made with `Circle()` and `trim` modifier shows full progress when 98% completion used in a small frame


I use a custom ProgressIndicator made with Circle() and trim modifier as below. Problem happens when I use completed to 0.98 which is close to full.

struct ContentView: View {
    let remainingTimeStrokeColor: Color = .secondary
    let completedTimeStrokeColor: Color = .blue
    
    @State var completed: Double = 0.70
    // MARK: Use 0.98 to check the problem
    
    var body: some View {
        ZStack {
            Circle()
                .stroke(remainingTimeStrokeColor, style: StrokeStyle(lineWidth: 15))
                .frame(width: 200, height: 200)
            Circle()
                .trim(from: 0, to: completed)
                .stroke(completedTimeStrokeColor, style: StrokeStyle(lineWidth: 15, lineCap: .square))
                .rotationEffect(.degrees(-90))
                .frame(width: 200, height: 200)
        }
    }
}

The normal output with completed = 0.75: 0.75 completion

When it sets to completed = 0.98, then it looks like fully completed. This is my main problem.

Output for completed = 0.98: 0.98 completion

It shows fully completed when using 0.98 or more close to completed value.

What I tried

After many trials and error I figured out that if I don't use the .frame(width: 200, height: 200) or I use larger frame to show the progress view then I can see the 98% completion properly.

But according to my UI guide provided for my product I have to use this small size progress view.


Solution

  • This is because you used .square for the blue circle's line cap.

    A line with a squared-off end. Core Graphics extends the line beyond the endpoint of the path for a distance equal to half the line width.

    For a line width of 15, the stroke at both ends of the line extends beyond the line by 7.5 each, so the line's length is extended by 15 in total. This causes the "gap" that you expect between the two ends to disappear.

    For a circle with radius 100, we can estimate the length of the "gap" that should be between the two ends of the line, by calculating:

    tan(.pi * 2 * 0.02) * 100
    

    This turns out to be about 12, which is less than 15.

    Use the default .butt line cap instead.