根据保存的状态自动激活 SwiftUI 中的动画

Activating an animation in SwiftUI automatically based on saved state

我正在尝试编写一个显示 3 个按钮的视图,我无法让动画在加载时启动。

当一个按钮被点击时,我希望它动画直到:

  1. 第二次点击
  2. 点击了 3 个按钮中的另一个

我已经使用 @Environment 对象存储 运行 状态的代码。它很好地在 3 个按钮之间切换:

代码在这里:

struct ContentView : View {

    @EnvironmentObject var model : ModelClockToggle



    var body: some View {

        VStack {

            ForEach(0...2) { timerButton in

             ActivityBreak(myId: timerButton)
                .padding()

            }
        }
    }
}


import SwiftUI

struct ActivityBreak : View {
    var myId: Int
    @EnvironmentObject var model : ModelClockToggle

    let anim1 = Animation.basic(duration: 1.0, curve: .easeInOut).repeatCount(Int.max)
    let noAni = Animation.basic(duration: 0.2, curve: .easeInOut).repeatCount(0)

    var body: some View {

            return Circle()
                .foregroundColor(.red)

                .scaleEffect(self.model.amIRunning(clock: self.myId) ? 1.0 : 0.6)
                .animation( self.model.amIRunning(clock: self.myId) ? anim1 : noAni )
                .tapAction {

                    self.model.toggle(clock: self.myId)



            }

    }
}

为了完整性,模型是:

import Foundation
import SwiftUI
import Combine


class ModelClockToggle: BindableObject  {

    let didChange = PassthroughSubject<ModelClockToggle, Never>()


    private var clocksOn: [Bool]  = [false,false,false]

    init() {
        clocksOn = []
        clocksOn.append(UserDefaults.standard.bool(forKey: "toggle1"))
        clocksOn.append(UserDefaults.standard.bool(forKey: "toggle2"))
        clocksOn.append(UserDefaults.standard.bool(forKey: "toggle3"))
        debugPrint(clocksOn)

    }



    func toggle(clock: Int) {
        debugPrint(#function)


        if clocksOn[clock] {
            clocksOn[clock].toggle()





        } else {
            clocksOn  = [false,false,false]
            clocksOn[clock].toggle()
        }
        saveState()
        didChange.send(self)
    }


    func amIRunning(clock: Int) -> Bool {
        debugPrint(clocksOn)

        return clocksOn[clock]


    }

    private func saveState() {

        UserDefaults.standard.set(clocksOn[0], forKey: "toggle1")
        UserDefaults.standard.set(clocksOn[1], forKey: "toggle2")
        UserDefaults.standard.set(clocksOn[2], forKey: "toggle3")


    }
}

如何根据传递到视图中的@Environment 对象使重复动画在加载时开始?现在 SwiftUI 似乎只在加载视图后才考虑状态更改。

我尝试添加一个 .onAppear 修饰符,但这意味着我必须使用不同的动画师 - 这会产生非常奇怪的效果。

感谢收到帮助。

在您的示例中,您使用的是隐式动画。这些动画会寻找任何可动画参数的变化,例如大小、位置、不透明度、颜色等。当 SwiftUI 检测到任何变化时,它会对其进行动画处理。

在您的特定情况下,圆在不活动时通常缩放为 0.6,在活动时缩放为 1.0。在非活动状态和活动状态之间变化,使您的 Circle 改变比例,并且这种变化是循环动画的。

但是,您的问题是最初以 1.0 比例加载的 Circle(因为模型说它处于活动状态)不会检测到变化:它从 1.0 开始并保持在 1.0。所以没有什么动画。

在您的评论中,您提到了一个解决方案,即让模型推迟加载 Circle 状态的状态。这样,您的视图首先被创建,然后您要求模型加载状态,然后您的视图中有一个可以动画的变化。这行得通,但是有一个问题。

您正在使模型的行为依赖于视图。当它真的应该相反时。假设屏幕上有两个视图实例。视时间而定,一个会启动正常,但另一个不会。

解决它的方法是确保整个逻辑都由视图本身处理。您想要完成的是,您的 Circle always 以 0.6 的比例创建。然后,您检查模型以查看 Circel 是否应该处于活动状态。如果是这样,您立即将其更改为 1.0。这样可以保证视图的动画效果。

这是一个可能的解决方案,它使用一个名为 booted@State 变量来跟踪它。您的圈子将始终 以 0.6 的比例创建,但是一旦调用 onAppear() 方法,视图将缩放到 1.0(如果处于活动状态),并产生相应的动画。

struct ActivityBreak : View {
    var myId: Int
    @EnvironmentObject var model : ModelClockToggle
    @State private var booted: Bool = false

    // Beta 4
    let anim1 = Animation.easeInOut(duration: 1.0).repeatCount(Int.max)
    let noAni = Animation.easeInOut(duration: 0.2).repeatCount(0)

    // Beta 3
    // let anim1 = Animation.basic(duration: 1.0, curve: .easeInOut).repeatCount(Int.max)
    // let noAni = Animation.basic(duration: 0.2, curve: .easeInOut).repeatCount(0)

    var body: some View {

            return Circle()
                .foregroundColor(.red)

                .scaleEffect(!booted ? 0.6 : self.model.amIRunning(clock: self.myId) ? 1.0 : 0.6)
                .animation( self.model.amIRunning(clock: self.myId) ? anim1 : noAni )
                .tapAction {
                    self.model.toggle(clock: self.myId)
                }
                .onAppear {
                    self.booted = true
                }


    }
}