首页 > 解决方案 > 停止 swiftUI 动画并跳转到目标值

问题描述

我正在为我的应用程序中的某些场景制作剩余时间的动画。我有一个问题,当用户通过转到下一个场景来中断这个场景时,动画不会停止

此代码是正在发生的事情的简化版本:

struct ContentView: View {
    @State private var remaining: Int = 40
    @State private var sceneIndex: Int = 0

    var body: some View {
        VStack(spacing: 30) {
            Text("Remaining:")
            EmptyView().modifier(DoubleModifier(value: remaining))

            Button(action: {
                self.sceneIndex += 1
                if self.sceneIndex == 1 {
                    withAnimation(.linear(duration: 10)) {
                        self.remaining = 30
                    }
                } else {
                    withAnimation(.linear(duration: 0)) {
                        self.remaining = 30 // changing this to any other value will stop the animation
                    }
                }
            }) {
                Text("Go to next scene")
            }
        }
    }
}
struct DoubleModifier: AnimatableModifier {
    private var value: Double

    init(value: Int) {
        self.value = Double(value)
    }

    var animatableData: Double {
        get { value }
        set { value = newValue }
    }

    func body(content: Content) -> some View {
        Text("\(Int(value)) seconds remaining")
    }
}

发生的情况是,当用户单击时,倒计时动画会初始化为 10 秒的持续时间,并将剩余时间设置为 40 到 30 秒。现在,当用户单击跳过按钮时,动画应该停止并转到剩余的 30 秒。但是,它仍然显示从 40 到 30(介于两者之间)的动画。

如果我在 else 语句中将 self.remaining 更改为 30,则该值不会更新,因为它已经设置为 30,但在内部它仍然是动画。如果我将 self.remaining 更改为 30 以外的任何其他有效值,例如 self.remaining = 29,动画确实会立即停止。

我可以通过以下方式替换 else 语句中的内容:

withAnimation(.linear(duration: 0)) {
   self.remaining = 29

   DispatchQueue.main.async {
       self.remaining = 30
   }
}

这确实有效,但感觉很混乱,在我的情况下,并不总是可以使用 DispatchQueue.main.async

还有另一种更好的方法来停止动画并跳到目标值吗?

是否有可能以某种方式为 else 语句中的 self.remaining = 30 分配某种标识符,以便 swiftUI 知道该值实际上确实发生了变化,即使它实际上并没有让 swiftUI 停止动画?

标签: swiftui

解决方案


目前尚不清楚从场景到场景逐步计划的逻辑是什么,但现在假设的目标可以通过以下方式实现body

var body: some View {
    VStack(spacing: 30) {
        Text("Remaining:")
        EmptyView().modifier(DoubleModifier(value: remaining)).id(sceneIndex)

        Button(action: {
            self.sceneIndex += 1
            withAnimation(.linear(duration: 10)) {
                self.remaining = 30
            }
        }) {
            Text("Go to next scene")
        }
    }
}

推荐阅读