如何通过更改非状态变量来触发 SwiftUI 动画

How to trigger SwiftUI animation via change of non-State variable

使用 .onAppear() 可以很容易地在视图出现时开始播放动画。但我想在视图的非状态变量之一发生变化时执行重复动画。一个例子:

这是一个视图,每当它的 throbbing 外部设置的参数是 true:

时,我都想“跳动”
struct MyCircle: View {
    var throbbing: Bool
    @State var scale = 1.0
    var body: some View {
        Circle()
            .frame(width: 100 * scale, height: 100 * scale)
            .foregroundColor(.blue)
            .animation(.easeInOut.repeatForever(), value: scale)
            .onAppear { scale = 1.2 }
    }
}

目前代码立即开始跳动,与 throbbing 变量无关。

但想象一下这个场景:

struct ContentView: View {
    @State var throb: Bool = false
    var body: some View {
        VStack {
            Button("Throb: \(throb ? "ON" : "OFF")") { throb.toggle() }
            MyCircle(throbbing: throb)
        }
    }
}

看起来像这样:

知道如何修改 MyCircle 以便在点击按钮时开始跳动并在再次点击时结束吗?

您可以使用onChange观看throbbing然后分配一个动画。如果为 true,则添加一个重复动画,如果为 false,则仅动画回到原始比例大小:

struct MyCircle: View {
    var throbbing: Bool
    @State private var scale : CGFloat = 0
    var body: some View {
        Circle()
            .frame(width: 100, height: 100)
            .scaleEffect(1 + scale)
            .foregroundColor(.blue)
            .onChange(of: throbbing) { newValue in
                if newValue {
                    withAnimation(.easeInOut.repeatForever()) {
                        scale = 0.2
                    }
                } else {
                    withAnimation {
                        scale = 0
                    }
                }
            }
    }
}

我刚刚在尝试实现您的目标时发现了两件有趣的事情。

  • value改变
  • 时将添加动画
  • 无法删除添加到视图的动画,我们需要通过删除它来从视图层次结构中删除动画视图id,将创建具有零动画的新视图。
import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    @State var throbbling = false
    @State var circleId = UUID()
    var body: some View {
        VStack {
            Toggle("Throbbling", isOn: $throbbling)
            circle.id(circleId)
                .scaleEffect(throbbling ? 1.2 : 1.0)
                .animation(.easeInOut.repeatForever(), value: throbbling)
                .onChange(of: throbbling) { newValue in
                    if newValue == false {
                        circleId = UUID()
                    }
                }
        }.padding()
    }
    
    @ViewBuilder
    var circle: some View {
        Circle().fill(.green).frame(width: 100, height: 100)
    }
}

PlaygroundPage.current.setLiveView(ContentView())