过渡动画在 SwiftUI 中不起作用

Transition animation not working in SwiftUI

我正在尝试创建一个非常简单的过渡动画,通过点击按钮 shows/hides 在屏幕中央显示一条消息:

struct ContentView: View {
    @State private var showMessage = false

    var body: some View {
        ZStack {
            Color.yellow

            VStack {
                Spacer()
                Button(action: {
                    withAnimation(.easeOut(duration: 3)) {
                        self.showMessage.toggle()
                    }
                }) {
                    Text("SHOW MESSAGE")
                }
            }

            if showMessage {
                Text("HELLO WORLD!")
                    .transition(.opacity)
            }
        }
    }
}

根据.transition(.opacity)动画的文档

A transition from transparent to opaque on insertion, and from opaque to transparent on removal.

消息应在 showMessage 状态 属性 变为真时淡入,当变为假时淡出。就我而言,情况并非如此。该消息以淡入淡出动画显示,但隐藏时根本没有动画。有什么想法吗?

编辑:查看下面从模拟器中获取的 gif 中的结果。

我更喜欢 Scott Gribben 的回答(见下文),但由于我无法删除这个(由于绿色勾选),我将保持原始答案不变。不过,我会争辩说,我确实认为这是一个错误。人们会期望 zIndex 由代码中出现的顺序视图隐式分配。


要解决这个问题,您可以将 if 语句嵌入到 VStack 中。

struct ContentView: View {
    @State private var showMessage = false

    var body: some View {
        ZStack {
            Color.yellow

            VStack {
                Spacer()
                Button(action: {
                    withAnimation(.easeOut(duration: 3)) {
                        self.showMessage.toggle()
                    }
                }) {
                    Text("SHOW MESSAGE")
                }
            }

            VStack {
                if showMessage {
                    Text("HELLO WORLD!")
                        .transition(.opacity)
                }
            }
        }
    }
}

问题是当视图在 ZStack 中来来去去时,它们的 "zIndex" 不会保持不变。发生的事情是,当 "showMessage" 从 true 变为 false 时,带有 "Hello World" 文本的 VStack 被放在堆栈的底部,黄色立即被绘制在它的顶部。它实际上正在淡出,但它是在黄色后面淡出的,所以你看不到它。

要修复它,您需要为堆栈中的每个视图明确指定 "zIndex",以便它们始终保持不变 - 如下所示:

struct ContentView: View {
@State private var showMessage = false

var body: some View {
    ZStack {
        Color.yellow.zIndex(0)

        VStack {
            Spacer()
            Button(action: {
                withAnimation(.easeOut(duration: 3)) {
                    self.showMessage.toggle()
                }
            }) {
                Text("SHOW MESSAGE")
            }
        }.zIndex(1)

        if showMessage {
            Text("HELLO WORLD!")
                .transition(.opacity)
                .zIndex(2)
        }
    }
}

}

我的发现是不透明度过渡并不总是有效。 (然而,幻灯片与 .animation 结合使用。)

.transition(.opacity) //does not always work

如果我将其编写为自定义动画,它确实有效:

.transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2))) 
.zIndex(1)

我认为这是 canvas 的问题。今天早上我在玩转换,虽然在 canvas 上不起作用,但它们似乎在模拟器中起作用。试一试。我已经向 Apple 报告了这个错误。

zIndex可能会导致动画被打断时断掉。将要应用过渡的视图包装在 VStackHStack 或任何其他容器中。

我在 swiftUI_preview 中发现了一个动画错误。当您在代码中使用过渡动画并希望在 SwiftUI_preview 中看到它时,它不会显示动画或仅在某些视图随动画消失时显示。为了解决这个问题,你只需要在 VStack 的预览中添加你的视图。像这样:

struct test_UI: View {
    @State var isShowSideBar = false
    var body: some View {
        ZStack {
            Button("ShowMenu") {
                withAnimation {
                    isShowSideBar.toggle()
                }
                
            }
            if isShowSideBar {
                SideBarView()
                    .transition(.slide)
            }
        }
    }
}
struct SomeView_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
           SomeView()
        }
    }
}

在此之后,所有动画都会发生。

我刚刚放弃了 .transition。它只是不工作。我改为动画视图的偏移量,更可靠:

首先我为偏移创建了一个状态变量:

@State private var offset: CGFloat = 200

其次,我为它设置了 VStack 的偏移量。然后,在其 .onAppear() 中,我使用动画将偏移量改回 0:

        VStack{
            Spacer()
            HStack{
                Spacer()
                Image("MyImage")
            }
        }
        .offset(x: offset)
        .onAppear {
            withAnimation(.easeOut(duration: 2.5)) {
                offset = 0
            }
        }

下面的代码应该可以工作。

import SwiftUI

struct SwiftUITest: View {
    
    @State private var isAnimated:Bool = false
  
    var body: some View {
        ZStack(alignment:.bottom) {
            VStack{
                Spacer()
                Button("Slide View"){
                    withAnimation(.easeInOut) {
                        isAnimated.toggle()
                    }
                    
                }
                Spacer()
                Spacer()
           
            }
            if isAnimated {
                RoundedRectangle(cornerRadius: 16).frame(height: UIScreen.main.bounds.height/2)
                    .transition(.slide)

            }
            
            
        }.ignoresSafeArea()
    }
}

struct SwiftUITest_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            SwiftUITest()
        }
    }
}