0

This animation runs as expected when first viewed but the timing gets messed up at random when the app is exited and then entered (or if you navigate away from the screen and back). How can this code be changed to keep the animation timing consistent after leaving the screen and coming back?

The error can be recreated by exiting and coming back to the app a couple times.

Correct animation After several app exits and re-enters

import SwiftUI

struct ExampleView: View {
@State var isAnimating: Bool = false

let timing =  4.0
let maxCounter: Int = 3

var body: some View {
    ZStack {
        Circle()
            .stroke(
                Color.blue.opacity(isAnimating ? 0.0 : 1.0),
                style: StrokeStyle(lineWidth: isAnimating ? 0.0 : 15.0))
            .scaleEffect(isAnimating ? 1.0 : 0.0)
            .animation(
                Animation.easeOut(duration: timing)
                .repeatForever(autoreverses: false)
                .delay(Double(0) * timing / Double(maxCounter) / Double(maxCounter)), value: isAnimating)
        
        Circle()
            .stroke(
                Color.blue.opacity(isAnimating ? 0.0 : 1.0),
                style: StrokeStyle(lineWidth: isAnimating ? 0.0 : 15.0))
            .scaleEffect(isAnimating ? 1.0 : 0.0)
            .animation(
                Animation.easeOut(duration: timing)
                .repeatForever(autoreverses: false)
                .delay(Double(1) * timing / Double(maxCounter) / Double(maxCounter)), value: isAnimating)
        Circle()
            .stroke(
                Color.blue.opacity(isAnimating ? 0.0 : 1.0),
                style: StrokeStyle(lineWidth: isAnimating ? 0.0 : 15.0))
            .scaleEffect(isAnimating ? 1.0 : 0.0)
            .animation(
                Animation.easeOut(duration: timing)
                .repeatForever(autoreverses: false)
                .delay(Double(2) * timing / Double(maxCounter) / Double(maxCounter)), value: isAnimating)
    }
    .frame(width: 200, height: 200, alignment: .center)
    .onAppear {
        isAnimating = true
    }
}
}
Ryan
  • 630
  • 8
  • 20
  • But that is not enough! You have to should how you leave this view, another word: Give use the code that create the issue! – ios coder Dec 01 '21 at 22:08
  • Exiting the app and coming back will cause the issue. I will update the description to make this more clear. Thanks. – Ryan Dec 01 '21 at 22:10
  • Hmmm. I just tested again in a clean project to make sure of this. Try moving app to the background and then re-entering a few times. You should notice that the timing of the rings gets thrown off and eventually all the rings join and go at the same time. – Ryan Dec 01 '21 at 22:29
  • A similar issue was described here https://stackoverflow.com/questions/69601418/swiftui-synchronizing-multiple-swiftui-animations-with-delays Just not sure how this particular animation can be fixed. – Ryan Dec 01 '21 at 22:41

1 Answers1

1

Here is a right way for you, it needs to use DispatchQueue and scenePhase:

PS: I noticed lots of complaining from Xcode with your original code! I refactored your code and solved those complaining as well.

struct ExampleView: View { 
    var body: some View {
        ZStack {
            CustomCircleAnimationView(delayValue: 0.0)
            CustomCircleAnimationView(delayValue: 1.0)
            CustomCircleAnimationView(delayValue: 2.0)
        }
        .frame(width: 200, height: 200)  
    }
}


struct CustomCircleAnimationView: View {
    
    @Environment(\.scenePhase) private var scenePhase
    
    let delay: Double
    private let timing: Double
    private let maxCounter: Double
    @State private var startAnimation: Bool = false
    
    init(timing: Double =  4.0, maxCounter: Double = 3.0, delayValue: Double) {
        self.timing = timing
        self.maxCounter = maxCounter
        self.delay = (delayValue*timing)/(maxCounter*maxCounter)
    }
    
    var body: some View { if (scenePhase == .active) { circle } }
    
    var circle: some View {
        
        return Circle()
            .stroke(Color.blue, style: StrokeStyle(lineWidth: startAnimation ? 0.001 : 15.0))
            .scaleEffect(startAnimation ? 1.0 : 0.001)
            .opacity(startAnimation ? 0.001 : 1.0)
            .onAppear { DispatchQueue.main.async { startAnimation = true } }
            .onDisappear { startAnimation = false }
            .animation(Animation.easeOut(duration: timing).repeatForever(autoreverses: false).delay(delay), value: startAnimation)
        
    }
}
ios coder
  • 1
  • 4
  • 31
  • 91