在SwiftUI中连接动画

37

我正在使用SwiftUI制作一个相对复杂的动画,想知道将各个动画阶段链接在一起的最佳/最优雅的方法是什么。

假设我有一个视图,首先需要进行缩放,然后等待几秒钟,然后淡出(然后等几秒钟并无限期地重新开始)。

如果我尝试在同一个视图/堆栈上使用多个withAnimation()块,它们会相互干扰并破坏动画。

到目前为止,我能想到的最好办法是在初始视图的.onAppear()修改器上调用自定义函数,在该函数中,每个动画阶段都有一个带延迟的withAnimation()块。因此,它基本上看起来像这样:

func doAnimations() {
  withAnimation(...)

  DispatchQueue.main.asyncAfter(...)
    withAnimation(...)

  DispatchQueue.main.asyncAfter(...)
    withAnimation(...)

  ...

}

内容变得相当长,而且不太“美观”。我相信一定有更好/更好的方法来做到这一点,但迄今为止我尝试的所有方法都没有给我想要的确切流程。

任何想法/建议/提示将不胜感激。谢谢!

3个回答

45

正如其他回答中提到的那样,在SwiftUI中目前没有链接动画的机制,但您不一定需要使用手动计时器。相反,您可以在链接动画上使用delay函数:

withAnimation(Animation.easeIn(duration: 1.23)) {
    self.doSomethingFirst()
}

withAnimation(Animation.easeOut(duration: 4.56).delay(1.23)) {
    self.thenDoSomethingElse()
}

withAnimation(Animation.default.delay(1.23 + 4.56)) {
    self.andThenDoAThirdThing()
}

我发现这种方法可以实现更为一致的流畅动画,比使用DispatchQueueTimer要好,可能是因为它在所有动画中使用相同的调度程序。

调整所有延迟和持续时间可能很麻烦,因此一个雄心勃勃的开发者可能会将计算抽象成一些全局的withChainedAnimation函数来代替你处理。


2
这个对你还有效吗?在我的iOS 16上无法工作。我在这里发布了一个带有示例的问题:https://stackoverflow.com/questions/73993064/how-can-i-chain-multiple-animations-to-show-then-hide-a-view-using-withanimation - gohnjanotis
延迟是否有保障?我不觉得使用硬编码的动画延迟很安全。 - marticztn
1
我发现在iOS 16中延迟部分没有起作用。我用DispatchQueue.main.asyncAfter(deadline: .now() + x.xx)代替了它,这个方法可行。 - C6Silver

7
使用计时器是有效的。以下是我自己项目中的示例:
@State private var isShowing = true
@State private var timer: Timer?

...

func askQuestion() {
    withAnimation(Animation.easeInOut(duration: 1).delay(0.5)) {
        isShowing.toggle()
    }
    timer = Timer.scheduledTimer(withTimeInterval: 1.6, repeats: false) { _ in
        withAnimation(.easeInOut(duration: 1)) {
            self.isShowing.toggle()
        }
        self.timer?.invalidate()
    }

    // code here executes before the timer is triggered.

}

3

很抱歉,目前还不支持像关键帧这样的东西。至少他们可以添加一个 onAnimationEnd()…但是没有这样的东西。

我成功的地方在于动画形状路径。虽然没有关键帧,但你有更多控制权,因为你可以定义自己的“AnimatableData”。例如,请查看我的回答:https://dev59.com/kLTna4cB1Zd3GeqPBuOB#56885066

在那种情况下,基本上是一条弧线旋转,但从零长逐渐增长到某个长度,并在转弯结束时逐渐回到零长度。动画分为3个阶段:首先,弧的一端移动,但另一端不移动。然后它们以相同的速度一起移动,最后第二个端点达到第一个端点。我的第一次尝试是使用DispatchQueue的想法,它起作用了,但我同意:它非常丑陋。然后我找到了如何正确使用AnimatableData。所以...如果你正在动画路径,那么你很幸运。否则,似乎我们将不得不等待更优雅代码的可能性。


感谢您的快速回复。让我问一下 - 如果您需要从另一个问题中获取动画,当它完成一个完整的圆圈时停止动画,然后淡出、淡入并重新启动动画,您能想到一种不添加DispatchQueue的方法吗? - Rony Rozen
从我的角度来看,我无法想到一种方法 :-( 我尝试同时设置两个动画,其中一个使用animation().delay(),以便在第一个动画完成后开始。但是我放弃了这个想法,因为我并不成功。但也许这是你可以探索的东西。 - kontiki
1
是的,我也试过那个方法,但没什么好运... :[ 再次感谢,祝我们好运... :] - Rony Rozen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接