在 .onAppear 中切换动画时,SwiftUI 出现意外动画(使用 GeometryReader 的大小)

Qua*_*ntm 4 animation ios swift swiftui

我有一个奇怪的动画行为SwiftUI。我尝试创建一个最小视图来演示它,如下所示。

我想用淡入淡出和缩放效果制作三个圆圈的动画(请参阅下面的“我的期望”栏)。但是,圆圈​​的大小取决于视图的宽度,因此我使用 aGeometryReader来实现。

我想在 中启动动画.onAppear(perform:),但在调用时,GeometryReader尚未设置size属性。我最终得到的是你在“Unwanted Animation 1”中看到的动画。这是因为帧被动画化为.zero正确的尺寸。

但是,每当我尝试通过添加修改器来禁用帧的动画时.animation(nil, value: size),我都会得到极其奇怪的动画行为(请参阅“不需要的动画 2”)。这一点我完全不明白。它以某种方式为动画添加了水平平移,这使得它看起来更糟糕。您知道这里发生了什么以及如何解决这个问题吗?

奇怪的是,如果我使用如下所示的显式动画,一切都会正常工作:

.onAppear {
    withAnimation {
      show.toggle()
    }
}
Run Code Online (Sandbox Code Playgroud)

但我想了解这里发生了什么。

谢谢!

更新:

替换.onAppear(perform:)为以下代码是否合理?这只会在视图的生命周期中触发一次,即当size更改.zero为正确值时。

.onChange(of: size) { [size] newValue in
    guard size == .zero else { return }
    show.toggle()
}
Run Code Online (Sandbox Code Playgroud)
我的期望 不需要的动画 1 不需要的动画2
在此输入图像描述 在此输入图像描述 在此输入图像描述
import SwiftUI

struct TestView: View {
    @State private var show = false
    @State private var size: CGSize = .zero

    var body: some View {
        VStack {
            circle
            circle
            circle
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .contentShape(Rectangle())
        .background {
            GeometryReader { proxy in
                Color.clear.onAppear { size = proxy.size }
            }
        }
        .onAppear { show.toggle() }
    }

    private var circle: some View {
        Circle()
            .frame(width: circleSize, height: circleSize)
            .animation(nil, value: size) // This make the circles animate in from the side for some reason (see "Strange Animation 2")
            .opacity(show ? 1 : 0)
            .scaleEffect(show ? 1 : 2)
            .animation(.easeInOut(duration: 1), value: show)
    }

    private var circleSize: Double {
        size.width * 0.2 // Everything works fine if this is a constant
    }
}


struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}
Run Code Online (Sandbox Code Playgroud)

Asp*_*eri 7

实际大小在第一次布局之后已知,但在之前.onAppear调用,因此布局(包括可动画化的帧更改)处于动画状态下。

为了解决这个问题,我们需要稍微延迟状态更改(直到第一次布局/渲染完成),例如

.onAppear {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
        show.toggle()
    }
}
Run Code Online (Sandbox Code Playgroud)

...这就是为什么withAnimation也有效 - 它实际上将关闭调用延迟到下一个周期。

使用 Xcode 13 / iOS 15 进行测试