iosanimationswiftui

SwiftUI animations go out of sync with each other


I'm trying to animate a few different properties on different views. The animations are all using the same Animation (with the same duration), and they are all driven by the same property. However, I find that they quite quickly go out of sync with each other, and I don't understand why. Here's my code:

import SwiftUI

struct ContentView: View {
    @State var isAnimating = false

    var body: some View {
        ZStack {
            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.yellow)
                .offset(x: isAnimating ? -30 : 0)
                .rotationEffect(isAnimating ? .degrees(-10) : .zero)

            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.green.opacity(0.5))
                .offset(x: isAnimating ? 30 : 0)
                .rotationEffect(isAnimating ? .degrees(10) : .zero)

            RoundedRectangle(cornerRadius: 8, style: .continuous)
                .fill(Color.red)
                .scaleEffect(isAnimating ? 1.1 : 1)
        }
        .frame(width: 100, height: 100)
        .padding()
        .onAppear {
            let animation = Animation.bouncy(duration: 0.5)
                .repeatForever(autoreverses: true)

            withAnimation(animation) {
                isAnimating = true
            }
        }
    }
}

#Preview {
    ContentView()
}

The only workaround I've found is to use a different Animation than Animation.bouncy, e.g. Animation.linear or Animation.easeInOut. In that case, the animations all stay in sync. That leads me to believe that this is related to Animation.bouncy somehow (other springy animations exhibit the same problem, e.g. Animation.spring).

This problem occurs in SwiftUI previews, on device, and on the iOS simulator (all using the latest software as of writing: iOS 17.4, Xcode 15.3).

Video: https://imgur.com/a/btVESSG


Solution

  • A workaround would be to go with

    Animation.interpolatingSpring(duration: 0.5)
    

    Since.bouncy is a customized spring animation, you can use interpolatingSpring and achieve the desired result

    Per the docs:

    An interpolating spring animation that uses a damped spring model to produce values in the range [0, 1] that are then used to interpolate within the [from, to] range of the animated property. Preserves velocity across overlapping animations by adding the effects of each animation.