Home > OS >  SwiftUI synchronizing multiple SwiftUI animations with delays
SwiftUI synchronizing multiple SwiftUI animations with delays

Time:10-18

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:

  • Toggling the whole animated container when the view is backgrounded/restored; this doesn't work. (I think because the container is kept around along with animation states even if it's not visible.)
  • Using DispatchQueue.main.asyncAfter(); this doesn't work any differently to the animation (since I presume it's the internal animation state itself being lost.)

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)
        }
    }
}

CodePudding user response:

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()
        }
    }
}
  • Related