Search code examples
animationswiftui

SwiftUI Animation reverses instead of stopping


I have a ring animation that goes around a button when the button is pressed. In my mind it should stop when the button is pressed, but it has a very weird behavior. It seems to reverse instead of reverting back to 0. How can I get it to go back to zero after the second button click? Here is my code:

Button with animating ring

struct ContentView: View {
   
    @State private var animationActive = false
    
    let animation = Animation
            .linear(duration: 10)
    
    var body: some View {
        ZStack {
            GeometryReader {geo in
                        ZStack {
                            //Image("RecordButton")
                            Button {
                                animationActive.toggle()
                            } label: {
                                Image("RecordButton")
                            }
                            ring(for: Color.white)
                                .animation(animation, value: animationActive)
                        }
            }.edgesIgnoringSafeArea(.all)
        }
    }
    
    func ring(for color: Color) -> some View {
        // Background ring
        Circle()
            .stroke(style: StrokeStyle(lineWidth: 20))
            .frame(width: 250)
            .foregroundStyle(.tertiary)
            .overlay {
                // Foreground ring
                Circle()
                    .trim(from: 0, to: animationActive ? 1 : 0)
                    .stroke(color.gradient,
                            style: StrokeStyle(lineWidth: 20, lineCap: .round))
                    .frame(width: 250)
            }
            .rotationEffect(.degrees(-90))
    }
}

Solution

  • You can change the .animation modifier to use a 0-duration animation (no animation at all) depending on animationActive:

    .animation(animationActive ? animation : .linear(duration: 0), value: animationActive)
    

    Note that the animation should be 0-duration when animationActive is false, because this expression is evaluated after animationActive has been toggled.

    Alternatively, you can remove .animation, and use withAnimation instead:

    Button {
        withAnimation(animationActive ? .linear(duration: 0) : animation) {
            animationActive.toggle()
        }
    } label: {
        Image("RecordButton")
    }