Reputation: 94723
I've developed a repeating animation that starts out silky smooth but starts to get quite choppy after 10 or 20 seconds doing nothing else on my device. This is the entire code for the view (it is set as the content view of a single view app):
import SwiftUI
struct MovingCirclesView: View {
@State var animationPercent: Double = 0
var body: some View {
ZStack {
MovingCircle(animationPercent: $animationPercent, size: 300, movementRadius: 100, startAngle: 0)
.offset(x: -200, y: -200)
MovingCircle(animationPercent: $animationPercent, size: 400, movementRadius: 120, startAngle: .pi * 3/4)
.offset(x: 50, y: 300)
MovingCircle(animationPercent: $animationPercent, size: 350, movementRadius: 200, startAngle: .pi * 5/4)
.offset(x: 10, y: 30)
MovingCircle(animationPercent: $animationPercent, size: 230, movementRadius: 80, startAngle: .pi * 1/2)
.offset(x: 220, y: -300)
MovingCircle(animationPercent: $animationPercent, size: 230, movementRadius: 150, startAngle: .pi)
.offset(x: 220, y: 100)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.animation(Animation.linear(duration: 30).repeatForever(autoreverses: false))
.onAppear() { self.animationPercent = 1 }
}
private struct MovingCircle: View, Animatable {
@Binding var animationPercent: Double
let size: CGFloat
let movementRadius: CGFloat
let startAngle: Double
var body: some View {
Circle()
.frame(width: size, height: size)
.foregroundColor(Color.white)
.opacity(0.1)
.offset(angle: .radians(.pi * 2 * self.animationPercent + self.startAngle), radius: self.movementRadius)
}
}
}
struct MovingCirclesView_Previews: PreviewProvider {
static var previews: some View {
MovingCirclesView()
.background(Color.black)
.edgesIgnoringSafeArea(.all)
}
}
struct AngularOffset: AnimatableModifier {
var angle: Angle
var radius: CGFloat
var animatableData: AnimatablePair<Double, CGFloat> {
get {
return AnimatablePair(angle.radians, radius)
}
set {
self.angle = .radians(newValue.first)
self.radius = newValue.second
}
}
func body(content: Content) -> some View {
return content.offset(CGSize(
width: CGFloat(cos(self.angle.radians)) * self.radius,
height: CGFloat(sin(self.angle.radians)) * self.radius
))
}
}
extension View {
func offset(angle: Angle, radius: CGFloat) -> some View {
ModifiedContent(content: self, modifier: AngularOffset(angle: angle, radius: radius))
}
}
The general idea is that there are a series of semi-transparent circles slowly moving in circles. I'm concerned this will not be worth the energy usage and was planning to profile anyway, but to my surprise, the animation seems to get bogged down rather quickly. I profiled it on an iPhone X and I don't see any increase in CPU usage nor Memory usage over time as the animation gets more and more choppy.
Does anyone have an idea of why the animation gets choppy? Anything I can do to fix that or do I have to throw out this idea?
Upvotes: 3
Views: 2626
Reputation: 257709
As on now it seems drawingGroup
does not have such positive effect as was before, because with used .animation(.., value:)
animation works properly and with low resource consumption.
Here is a solution - activating Metal by using .drawingGroup
and using explicit animation.
Works fine with Xcode 11.4 / iOS 13.4 - tested during 5 mins, CPU load 4-5%
ZStack {
// .. circles here
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.drawingGroup()
.onAppear() {
withAnimation(Animation.linear(duration: 30).repeatForever(autoreverses: false)) {
self.animationPercent = 1
}
}
Note: findings - it looks like implicit animation recreates update stack in this case again and again, so they multiplied, but explicit animation activated only once.
Upvotes: 3