1

I have on-going animations in my app that are triggered on onAppear and configured using withAnimation, updating a @State property.

Each time the view appears, the animation runs a little faster than before, so if the view is displayed, then covered by a modal display or hidden in navigation and later reappears, the animation starts to go really, really fast – maybe 10 or 20 times faster than it should.

Here's the code...

struct HueRotationAnimation: ViewModifier {
    @State var hueRotationValue: Double
    func body(content: Content) -> some View {
        content
            .hueRotation(Angle(degrees: hueRotationValue))
            .onAppear() {
                DispatchQueue.main.async {
                    withAnimation(.linear(duration: 20).repeatForever(autoreverses: false)) {
                        hueRotationValue += 360
                    }
                }
            }
    }
}

struct GradientCircle: View {
    var gradient: Gradient
    @State var hueRotationValue: Double = Double.random(in: 0..<360)
    
    var body: some View {
        GeometryReader { geometry in
            Circle()
                .fill(
                    radialGradient(geometry: geometry, gradient: gradient)
                )
                .modifier(HueRotationAnimation(hueRotationValue: hueRotationValue))
        }
    }
}

func radialGradient(geometry: GeometryProxy, gradient: Gradient) -> RadialGradient {
    RadialGradient(gradient: gradient,
                   center: .init(x: 0.82, y: 0.85),
                   startRadius: 0.0,
                   endRadius: max(geometry.size.width, geometry.size.height) * 0.8)
}

Any idea what's causing that speed up each time the view re-appears? Any suggestions to solve this problem?

(Note: this is running Xcode 13.0 beta 4)

AVS
  • 373
  • 3
  • 19

2 Answers2

3

I think it has to do with your += 360 because every time it appears the number of degrees it needs to rotate increases by another 360 degrees. Instead of adding the 360 in the appear try setting a state boolean for when the animation should run. Try the code below and see if it works for you.

struct HueRotationAnimation: ViewModifier {
@State var hueRotationValue: Double
@State private var animated = false

func body(content: Content) -> some View {
    content
        .hueRotation(Angle(degrees: animated ? hueRotationValue : hueRotationValue + 360)).animation(.linear(duration: 20).repeatForever(autoreverses: false))
        .onAppear() {
            self.animated.toggle()
        }
}
}

This way the 360 animation should remain 360 degrees and the speed of the animation should not change.

yawnobleix
  • 1,204
  • 10
  • 21
1

To advance, @yawnobleix's answer, I added a .onDisappear toggle. It address some other bugs when the view appears > disappears > appears.

struct HueRotationAnimation: ViewModifier {
    @State var hueRotationValue: Double
    @State private var animated = false
    func body(content: Content) -> some View {
        content
            .hueRotation(Angle(degrees: animated ? hueRotationValue : hueRotationValue + 360)).animation(.linear(duration: 20).repeatForever(autoreverses: false))
            .onAppear {
                animated = true
            }
            .onDisappear {
                animated = false
            }
    }
}
AVS
  • 373
  • 3
  • 19