Search code examples
animationswiftuicpu

High CPU by circle animation with gradient


By using the following SwiftUI code the CPU consumption caused by my app is up to 55% when the app is running in XCode (simulator) with an M1.

On the real iPhone the CPU is up to 52%

FYI: the circleProgess is being updated every 0.1 seconds.

Circle()
    .trim(from: 0.0, to: circleProgress)
    .stroke(style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
    .fill(Color.pink.gradient)
    .rotationEffect(Angle(degrees: 270.0))
    .animation(.default, value: circleProgress)
    .frame(width: 100, height: 100)

I found out that the high CPU is caused not only but mainly by the Color.pink.gradient line.

If I use the same code but with Color.pink instead of Color.pink.gradient the CPU usage is around 15% on the XCode simulator. On the real device e.g. iPhone 12 the CPU is still up to 35%

Here a reproducible example

struct ContentView: View {
    @ObservedObject var someTimer = SomeTimer()
    
    var body: some View {
        AnimatedCircle(circleProgress: $someTimer.circleProgess, size: 100)
        AnimatedCircle(circleProgress: $someTimer.circleProgess, size: 400)
        
        Spacer()
        
        Button(action: someTimer.start, label: {
            Text("Start animation")
        })
        
        Spacer()
    }
}


class SomeTimer : ObservableObject {
    @Published var circleProgess = 1.0
    
    func start() {
        circleProgess = 1.0
        Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) {_ in
            self.circleProgess -= 0.001
        }
    }
}

struct AnimatedCircle: View {
    @Binding var circleProgress : Double
    var size : Int
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: CGFloat(circleProgress))
            .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
            .fill(Color.pink.gradient)
            //.fill(Color.pink)
            .rotationEffect(Angle(degrees: 270.0))
            .animation(.default, value: CGFloat(circleProgress))
            .frame(width: CGFloat(size), height: CGFloat(size))
    }
}

There is a huge CPU consumption difference by running AnimatedCircle() with .fill(Color.pink.gradient) or with .fill(Color.pink)

Any ideas how I can use this animation with Color.pink.gradient and still keep the CPU usage much lower than 55%?
Thanks


Solution

  • Using your Timer example, I found that the app was using about 47% of a CPU when running on an iPhone 15 simulator with iOS 17.5.

    It uses less CPU if you fill the circle first, then apply the trimmed shape as a mask:

    Circle()
        .fill(Color.pink.gradient)
        .frame(width: CGFloat(size), height: CGFloat(size))
        .mask {
            Circle()
                .inset(by: 10) // half the line width
                .trim(from: 0.0, to: CGFloat(circleProgress))
                .stroke(style: StrokeStyle(lineWidth: 20, lineCap: .round, lineJoin: .round))
                .rotationEffect(Angle(degrees: 270.0))
                .animation(.default, value: CGFloat(circleProgress))
        }
    

    In my test, this only used about 8% CPU in the simulator.