Forest Katsch
Forest Katsch

Reputation: 1555

SwiftUI synchronizing multiple SwiftUI animations with delays

I am trying to build a view with multiple items that should animate in sync with each other; for example, if one item is at 25%, the other three should be 50%, 75%, and 100%.

The naive option of just using .delay(delay) seems to work perfectly at first, but once I switch apps, the different animations are all reset to be perfectly offset (without any offsets anymore). I've tried a number of hacky workarounds, including:

Note that I am not at all invested in this particular solution; any suggestions on a better way to accomplish this problem are very welcome.

Before switching apps After switching apps
before enter image description here

struct MyRepeatingItemView: View {
    var delay: Double
    
    @State
    var isTranslated = false
    
    var body: some View {
        Color.red
            .opacity(0.2)
            .frame(width: 256, height: 256)
            .scaleEffect(isTranslated ? 1 : 0.01)
            .onAppear {
                withAnimation(Animation.linear(duration: 4).repeatForever(autoreverses: false).delay(delay)) {
                    isTranslated.toggle()
                }
            }
    }
}

struct MyRepeatingContainerView: View {
    
    var body: some View {
        ZStack {
            Color.blue
                .frame(width: 256, height: 2)
            Color.blue
                .frame(width: 2, height: 256)
            MyRepeatingItemView(delay: 0.0)
            MyRepeatingItemView(delay: 1.0)
            MyRepeatingItemView(delay: 2.0)
            MyRepeatingItemView(delay: 3.0)
        }
    }
}

Upvotes: 1

Views: 1661

Answers (1)

Yrb
Yrb

Reputation: 9755

The fix for the out of sync views is to put them all in one view and that view syncs everything simultaneously. It took me some time to work out the pattern, but here it is:

struct MyRepeatingItemView: View {
    
    @State var isTranslated = false
    
    var body: some View {
        ZStack {
            Rectangle()
                .fill(Color.red)
                .frame(width: 256, height: 256)
                .opacity(isTranslated ? 0 : 0.25)
                .scaleEffect(isTranslated ? 1 : 0.75)
            Rectangle()
                .fill(Color.red)
                .frame(width: 256, height: 256)
                .opacity(0.25)
                .scaleEffect(isTranslated ? 0.75 : 0.5)
            Rectangle()
                .fill(Color.red)
                .frame(width: 256, height: 256)
                .opacity(0.25)
                .scaleEffect(isTranslated ? 0.5 : 0.25)
            Rectangle()
                .fill(Color.red)
                .frame(width: 256, height: 256)
                .opacity(0.25)
                .scaleEffect(isTranslated ? 0.25 : 0)
        }
        .onAppear {
            withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                isTranslated.toggle()
            }
        }
    }
}

struct MyRepeatingContainerView: View {
    
    var body: some View {
        ZStack {
            Color.blue
                .frame(width: 256, height: 2)
            Color.blue
                .frame(width: 2, height: 256)
            
            MyRepeatingItemView()
        }
    }
}

Upvotes: 1

Related Questions