Home > other >  SwiftUI .delay animation timing is incorrect after leaving and coming back to view
SwiftUI .delay animation timing is incorrect after leaving and coming back to view

Time:12-02

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

CodePudding user response:

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