【问题标题】:SwiftUI position and frame animation broken in iOS 14SwiftUI 位置和帧动画在 iOS 14 中被破坏
【发布时间】:2020-09-23 09:13:41
【问题描述】:

我有一个进度视图,其进度/宽度可以设置动画。该动画可自行运行。但是,在某些情况下,父视图会更改其布局,并在进度条上方添加一个新视图。然后,进度视图向下移动,为新视图腾出空间。这也是动画的。在 iOS 13 中同时发生这两种动画时,进度视图会向下移动,并且蓝色进度条的宽度会同时进行动画处理。在 iOS 14 中,进度视图向下移动,但蓝色进度条也使用它的动画来动画位置变化。在 iOS 13 中,它仅用于为宽度设置动画。这使得动画看起来好像进度条飞入了进度视图,而且是错误的,看起来很奇怪。

我使用以下代码复制了我的应用程序中的代码。此代码也用于录制视频。

struct MainView: View {
    @State var toggle = false
    @State var trim: CGFloat = 0.2

    var body: some View {
        VStack {
            Rectangle().frame(minHeight: 0, maxHeight: self.toggle ? 200 : 0)

            ProgressBarView(trim: self.$trim)

            Button(
                action: {
                    withAnimation {
                        self.toggle.toggle()
                        self.trim = self.toggle ? 0.8 : 0.2
                    }
                },
                label: {
                    Text("Toggle")
                })
        }
    }
}

struct ProgressBarView: View {
    @State var grow: Bool = false
    @Binding var trim: CGFloat

    var body: some View {
        ZStack(alignment: .leading) {
            GeometryReader { geo in
                Rectangle()
                    .opacity(0.1)
                    .zIndex(0)

                Rectangle()
                    .frame(
                        minWidth: 0,
                        maxWidth: self.grow
                            ? geo.frame(in: .global).width * self.trim
                            : 0
                    )
                    .animation(Animation.easeOut(duration: 1.2).delay(0.5))
                    .foregroundColor(Color(UIColor.systemBlue))
            }
        }
        .cornerRadius(10)
        .frame(height: 20)
        .onAppear(perform: {
            self.grow = true
        })
    }
}

更新

这是在我仍在运行 iOS 13.7 的 iPad 上使用相同代码的原始动画。我只是消除了延迟并增加了时间以使不同的动画更加明显。

更新 2

回答问题时有些混乱,所以我复制了一些框架并将它们放入屏幕截图中。我希望这有助于理解我的问题。左侧是 iOS 14 上带有不良行为的动画。如您所见,某些图像中没有出现蓝色进度条。在其他图像中,它只是部分可见。右侧是 iOS 13 动画。图像上的框架(以及所有其他框架)显示完全可见的蓝色进度条。它始终完全可见并位于背景之上。

我不明白 iOS 14(或者可能是 Swift 5.3 或其他任何版本)中的哪些变化导致动画不同,我找不到解决此问题的方法。

【问题讨论】:

    标签: swiftui ios14 swiftui-animation


    【解决方案1】:

    你好!我对ProgressBarView 结构做了一些改动。让我们先来看看不太重要的变化。

    • 删除了 GeometryReader 并将其替换为 screen.width。如果您使用容器内的栏来获取容器的宽度,请使用GeometryReader

    • 将矩形更改为胶囊。

    给重要的人

    • 我将栏的动画修饰符定位到触发进度条变化的布尔变量,即grow
    struct ProgressBarView: View {
        @State var grow: Bool = false
        @Binding var trim: CGFloat
        
        let screen = UIScreen.main.bounds
    
        var body: some View {
            VStack {
                ZStack(alignment: .leading) {
                    Capsule()
                        .foregroundColor(Color.black.opacity(0.1))
                    
                    Capsule()
                        .frame(width: self.grow ? screen.width * trim : 0)
                        .foregroundColor(.blue)
                        .animation(Animation.easeOut(duration: 1).delay(0.5), value: self.grow)
                }
                .frame(width: screen.width, height: 20)
                .onAppear {
                    self.grow = true
                }
            }
        }
    }
    

    更新 1.1

    • 我为 ZStack 添加了一个动画,用于将 bar 从 0 初始增加到 0.2
    • DispatchQueue 延迟更改为 0.18 秒。

    延迟变化的原因是,由于矩形的扩展正在改变条在y方向上的位置,因此我们将持续时间为1.2秒的动画效果应用于我们的位置变化不想要。因此 0.18 的延迟在矩形扩展后一点点开始条动画。这有点小技巧,但可以完成工作。如果我找到更好的解决方案,我将使用“更新 2.0”更新此答案。如果您找到更好的解决方案,请告诉我。

    代码

    struct MainView: View {
        @State var toggle = false
        @State var trim: CGFloat = 0.2
    
        var body: some View {
            VStack {
                Rectangle().frame(minHeight: 0, maxHeight: self.toggle ? 200 : 0)
    
                ProgressBarView(trim: self.$trim)
    
                Button(
                    action: {
                        withAnimation {
                            self.toggle.toggle()
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.18) {
                                self.trim = self.toggle ? 0.8 : 0.2
                            }
                        }
                    },
                    label: {
                        Text("Toggle")
                    })
            }
        }
    }
    
    struct ProgressBarView: View {
        @State var grow: Bool = false
        @Binding var trim: CGFloat
        
        let screen = UIScreen.main.bounds
    
        var body: some View {
            ZStack(alignment: .leading) {
                Capsule()
                    .opacity(0.1)
                
                Capsule()
                    .frame(width: self.grow ? screen.width * trim : 0)
                    .foregroundColor(.blue)
                    .animation(Animation.easeOut(duration: 1.2), value: self.trim)
            }
            .frame(width: screen.width, height: 20)
            .onAppear {
                self.grow = true
            }
            .animation(.easeOut(duration: 0.5), value: grow)
        }
    }
    

    希望对你有帮助。

    【讨论】:

    • 感谢您的回答!在我的应用程序中,我在另一个视图中使用 ProgressView,这就是我使用 GeometryReader 的原因。 self.grow 仅用于从 0 到 trim 的第一个动画。当trim 更改时,您基本上是在删除自定义动画。但我希望easeOut.delay 动画在trim 更改时起作用。我使用在 iOS 13.7 上运行的相同代码和正确的动画更新了我的答案。
    • 嘿@Yannick 我已经更新了我的答案。看看它,如果这就是你要找的,请告诉我。
    • 再次感谢您的更新。更新版本更接近我的原始动画。但是,仍然有一些帧蓝色进度条不在位置。 i.stack.imgur.com/rExUv.png
    • 嘿@Praduyt,感谢您的更新。如果您在动画进行时不更改状态,则动画将起作用。对于我的应用程序,它可以工作,但我仍然认为它是一种解决方法。我仍然很好奇导致这种行为的 iOS 14 中发生了什么变化。
    • 嘿@Yannick 我不确定发生了什么变化。
    猜你喜欢
    • 1970-01-01
    • 2021-01-09
    • 1970-01-01
    • 2012-04-26
    • 2021-01-13
    • 2021-09-22
    • 2023-03-11
    • 2021-01-28
    • 1970-01-01
    相关资源
    最近更新 更多