animationswiftui

SwiftUI - several of the same animations overlapping


I have a following view, on MacOS. The idea is that if I click the button, the symbol starts spinning in one direction, if I click the second time, it goes to the other direction. The code looks like this and works:

struct TestView: View {
    
    @State var open: Bool = false

    var body: some View {
        Button(action: {
            open.toggle()
        }, label: {
            Label("", systemImage: "gearshape.circle.fill")
                .font(.system(size: 50))
                .foregroundStyle(Color.black)
 
        })
        .symbolEffect(open ? .rotate.clockwise : .rotate.counterClockwise, value: self.open)
        .animation(.easeInOut(duration: 2), value:  self.open)
    }
}

But if I click the button for the second time too quickly, the first animation is first let to finish, and only then the second animation starts (if I click multiple times, only two animations are chained in this way, and the rest is ignored). However this is not the behaviour I want. If I click, I need the old animation to immediatelly stop and new animation immediatelly start from the point where the first animation was.

I cannot find anything about how such overlapping animations are intended to work, and how to influence them.


Solution

  • One workaround is to show two images with different symbol effects, then toggle the visibility according to the flag.

    The answer to how to animate symbols for only one way demonstrated how this technique can be used when the animation should only work one way (it was my answer). However, the case here is more difficult, because you want both variations to be animated. For this reason, it doesn't work well to use .opacity to toggle the visibility, because the effect is still triggered on the non-visible symbol every time the flag changes.

    So, instead of using .opacity, the visibility can be toggled by using if-else inside a ZStack. Then:

    struct TestView: View {
        @State private var open = false
        @State private var animate = false
        
        var body: some View {
            Button {
                open.toggle()
            } label: {
                ZStack {
                    if open {
                        Image(systemName: "gearshape.circle.fill")
                            .symbolEffect(.rotate.clockwise, value: animate)
                            .transition(.identity)
                    } else {
                        Image(systemName: "gearshape.circle.fill")
                            .symbolEffect(.rotate.counterClockwise, value: animate)
                            .transition(.identity)
                    }
                }
                .font(.system(size: 50))
                .foregroundStyle(.black)
                .onChange(of: open) {
                    animate.toggle()
                }
            }
            // .animation(.easeInOut(duration: 2), value: open) // not needed
        }
    }
    

    Animation