SwiftUI .delay 动画时序离开再回来查看不正确

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

此动画在第一次观看时按预期运行,但在应用程序退出然后进入时(或者如果您离开屏幕并返回),时间随机混乱。如何更改此代码以保持离开屏幕和返回后动画时间一致?

可以通过退出并返回应用几次来重现错误。

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

这里有一个适合你的方法,需要用到DispatchQueue和scenePhase:

PS:我注意到 Xcode 对您的原始代码有很多抱怨!我重构了您的代码并解决了那些抱怨的问题。

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