'SwiftUI - Pulsating Animation & Change Colour

I'm trying to change the colour of my animation based on the state of something. The colour change works but it animates the previous (orange) colour with it. I can't quite work out why both colours are showing. Any ideas?

struct PulsatingView: View {

    var state = 1

    func colourToShow() -> Color {
        switch state {
        case 0:
            return Color.red
        case 1:
            return Color.orange
        case 2:
            return Color.green
        default:
            return Color.orange
        }
    }

    @State var animate = false
    var body: some View {
        VStack {
            ZStack {
                Circle().fill(colourToShow().opacity(0.25)).frame(width: 40, height: 40).scaleEffect(self.animate ? 1 : 0)
                Circle().fill(colourToShow().opacity(0.35)).frame(width: 30, height: 30).scaleEffect(self.animate ? 1 : 0)
                Circle().fill(colourToShow().opacity(0.45)).frame(width: 15, height: 15).scaleEffect(self.animate ? 1 : 0)
                Circle().fill(colourToShow()).frame(width: 6.25, height: 6.25)
            }
            .onAppear { self.animate.toggle() }
            .animation(Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: true))
        }
    }
}


Solution 1:[1]

You change the color, but animating view, which is set up forever is not changed - it remains as set and continues as specified - forever. So it needs to re-set the animation.

Please find below a working full module demo code (tested with Xcode 11.2 / iOS 13.2). The idea is to use ObservableObject view model as it allows and refresh view and perform some actions on receive. So receiving color changes it is possible to reset and view color and animation.

Updated for Xcode 13.3 / iOS 15.4

demo

Main part is:

    }
    .onAppear { self.animate = true }
    .animation(animate ? Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: true) : .default, value: animate)
    .onChange(of: viewModel.colorIndex) { _ in
        self.animate = false                     // << here !!
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.animate = true                  // << here !!
        }
    }

Complete findings and code is here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1