【问题标题】:SwiftUI unexpected animations when toggling animations In .onAppear (using a GeometryReader's size)SwiftUI 在 .onAppear 中切换动画时出现意外动画(使用 GeometryReader 的大小)
【发布时间】:2022-01-19 07:21:38
【问题描述】:

我在SwiftUI 中有一个奇怪的动画行为。我试图创建一个最小的视图来演示它。

我想用淡入淡出和缩放效果在三个圆圈中制作动画(请参阅下面的“我的期望”栏)。但是,圆圈​​的大小取决于视图的宽度,所以我使用GeometryReader 来获得它。

我想在.onAppear(perform:) 中开始动画,但是在调用的时候,GeometryReader 还没有设置size 属性。我最终得到的是您在 “Unwanted Animation 1” 中看到的动画。这是因为帧从 .zero 动画到正确的大小。

但是,每当我尝试通过添加.animation(nil, value: size) 修饰符来禁用帧的动画时,我都会得到一个非常奇怪的动画行为(参见“Unwanted Animation 2”)。这个我完全不明白。它以某种方式为动画添加了水平平移,使其看起来更糟。任何想法这里发生了什么以及如何解决这个问题?

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

.onAppear {
    withAnimation {
      show.toggle()
    }
}

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

谢谢!

更新:

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

.onChange(of: size) { [size] newValue in
    guard size == .zero else { return }
    show.toggle()
}

What I Expect Unwanted Animation 1 Unwanted Animation 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()
    }
}

【问题讨论】:

    标签: ios swift animation swiftui


    【解决方案1】:

    真正的大小在第一个布局之后是已知的,但.onAppear 被称为之前,所以布局(包括帧变化,它是动画的)在动画之下。

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

    .onAppear {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
            show.toggle()
        }
    }
    

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

    使用 Xcode 13 / iOS 15 测试

    【讨论】:

    • 有意思,谢谢你的澄清!感谢您的回答,我有了一个想法,请参阅更新的问题。你怎么看? .onChange 应该只在视图生命周期开始时被调用一次。这样,就不需要固定的时间延迟。您认为这种方法有什么问题吗?
    • 不,onChange 将在父布局的每次更改时触发,因此动画将重新启动...但在您的特定 UI(我不知道)中,这可能永远不会发生。跨度>
    • 我可能会误解一些东西,但守卫语句不应该防止多个触发器吗?或者每次父视图更改布局时大小都会重置为 .zero?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-09
    • 2012-04-04
    • 2021-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多