【问题标题】:SwiftUI animation - toggled Boolean always ends up as trueSwiftUI 动画 - 切换的布尔值始终以 true 结束
【发布时间】:2021-12-21 16:12:28
【问题描述】:

我试图在我的应用程序中创建一个动画,当一个特定的动作发生时,它本质上会使给定元素的背景改变颜色并返回 x 次以创建一种“脉冲”效果。应用程序本身非常大,但我设法在一个非常基本的应用程序中重新创建了这个问题。

所以ContentView如下:

struct ContentView: View {
    struct Constants {
        static let animationDuration = 1.0
        static let backgroundAlpha: CGFloat = 0.6
    }
    
    @State var isAnimating = false
    @ObservedObject var viewModel = ContentViewViewModel()
    private let animation = Animation.easeInOut(duration: Constants.animationDuration).repeatCount(6, autoreverses: false)
    
    var body: some View {
        VStack {
            Text("Hello, world!")
                
                .padding()
            Button(action: {
                animate()
            }) {
                Text("Button")
                    .foregroundColor(Color.white)
            }
        }
        .background(isAnimating ? Color.red : Color.blue)
        .onReceive(viewModel.$shouldAnimate, perform: { _ in
            if viewModel.shouldAnimate {
                withAnimation(self.animation, {
                    self.isAnimating.toggle()
                })
            }
        })
    }
    
    func animate() {
        self.viewModel.isNew = true
    }
}

然后我的 viewModel 是:

import Combine
import SwiftUI

class ContentViewViewModel: ObservableObject {
    @Published var shouldAnimate = false
    @Published var isNew = false
    var cancellables = Set<AnyCancellable>()
    
    init() {
        $isNew
            .sink { result in
                if result {
                    self.shouldAnimate = true
                }
            }
            .store(in: &cancellables)
    }
}

所以我遵循的逻辑是,当点击按钮时,我们将“isNew”设置为 true。这又是一个发布者,当设置为 true 时,将“shouldAnimate”设置为 true。在 ContentView 中,当接收到 shouldAnimate 并且为 true 时,我们切换 VStack 的背景颜色 x 次。

我使用这个 'shouldAnimate' 发布属性的原因是因为在实际应用程序中,可能需要几个不同的动作来触发动画,因此将它绑定到一个我们可以监听的变量感觉更简单在 ContentView 中。

所以在上面的代码中,我们应该切换 isAnimating bool 6 次。所以,我们从 false 开始,然后切换如下:

1:真,2:假,3:真,4:假,5:真,6:假

所以我希望最终结果为 false,因此背景为白色。但是,这就是我得到的:

我尝试更改 repeatCount(以防我误解了计数的工作原理):

private let animation = Animation.easeInOut(duration: Constants.animationDuration).repeatCount(7, autoreverses: false)

我得到以下信息:

无论计数如何,我总是以 true 结束。

更新:

我现在已经设法通过使用以下循环获得我正在寻找的效果:

for i in 0...5 {
                    DispatchQueue.main.asyncAfter(deadline: .now() + Double(i), execute: {
                        withAnimation(self.animation, {
                            self.isAnimating.toggle()
                        })
                    })
                }

不确定这是不是最好的方法....

【问题讨论】:

  • 我认为您误解了Animations 的工作原理。当您将变量包装在 withAnimation() 中时,您实际上是在订阅动画以观察变量的变化。当发生这种变化时,动画就会运行。动画不会更改变量。它最终为真的原因是因为你只将它从假设置为真。你的self.isAnimating.toggle() 只运行一次。
  • 谢谢你 - 是的,我显然在这里以错误的方式查看动画......
  • SwiftUI Lab 有很多关于动画的博文。
  • 我看了你的动画,我不确定我能分辨出它们之间的区别。第一个和第二个示例中您不喜欢第三个示例中的哪些内容?

标签: animation swiftui toggle combine


【解决方案1】:

要了解发生了什么,了解CALayer 属性动画会有所帮助。

当您定义动画时,系统会捕获图层的状态并监视该图层的动画属性的变化。它记录动画期间播放的属性更改。为了呈现动画,它会创建一个处于初始状态的图层副本(presentationLayer)。然后,它用副本代替屏幕上的实际图层,并通过操纵表示层的动画属性来运行动画。

在这种情况下,当您开始动画时,系统会观察支持您的视图的 CALayer 发生的情况并捕获任何可动画属性(在本例中为背景颜色)的更改。然后它会创建一个presentationLayer 并重复播放这些属性更改。它不会重复运行您的代码 - 它正在更改表示层的属性。

换句话说,由于您在动画块中设置的示例,系统知道层的背景颜色属性应该来回切换的动画,但是动画来回切换背景颜色而无需再次运行您的代码。

【讨论】:

  • 感谢斯科特,这很有帮助。因此,在这种情况下,为了获得我正在寻找的效果,最“迅速”的方式是使用循环吗?我编辑了问题并举了一个例子。对我来说感觉不对,但它确实产生了预期的效果......
  • 您可以这样做,但您基本上放弃了 CALayer 属性动画的任何性能优势。您也许还可以创建一个CAAction 来切换您的变量并将其附加到动画中......但恐怕我已经很久没有做过很多CAAnimation 工作了,我不确定SwiftUI 为您提供了对所涉及层的何种访问权限。事实上,SwiftUI 可能正在使用一些非常相似但不完全是 CALayers 的东西来实现它的行为。
猜你喜欢
  • 2014-09-07
  • 1970-01-01
  • 2011-02-27
  • 2012-09-26
  • 2019-03-25
  • 2010-12-13
  • 2022-07-18
  • 2020-08-01
  • 1970-01-01
相关资源
最近更新 更多