过渡动画在 SwiftUI 中无法正常工作

问题描述 投票:0回答:10

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

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)
动画的文档

插入时从透明到不透明的过渡,以及从不透明的过渡 去除透明。

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

编辑: 请参阅下面从模拟器中获取的

gif
中的结果。

ios swift animation swiftui
10个回答
169
投票

问题是当视图在 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)
            }
        }
    }
}

88
投票

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

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

如果我把它写成自定义动画它确实有效:

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

28
投票

我在动画的 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()
        }
    }
}

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


12
投票

我相信这是画布的问题。今天早上我在玩过渡,虽然它们在画布上不起作用,但它们似乎在模拟器中起作用。试一试。我已经向 Apple 报告了这个错误。


9
投票

我更喜欢 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)
                }
            }
        }
    }
}

2
投票

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


2
投票

我刚刚放弃了 .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
            }
        }

1
投票

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

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()
        }
    }
}

0
投票

你应该放

 .id(showMessage) 

在你的 VStack 的主体之后,那应该对你有帮助。


0
投票

SwiftUI 自定义不透明度过渡

您可以扩展

in
out
不透明度过渡。

import SwiftUI

extension AnyTransition {
    static var inOpacity: AnyTransition {
        AnyTransition.modifier(
                        active: OpacityModifier(opacity: 0),
                      identity: OpacityModifier(opacity: 1)
        )
    }
    static var outOpacity: AnyTransition {
        AnyTransition.modifier(
                        active: OpacityModifier(opacity: 1),
                      identity: OpacityModifier(opacity: 0)
        )
    }
}
struct OpacityModifier : ViewModifier {
    let opacity: Double
    
    func body(content: Content) -> some View {
        content.opacity(opacity)
    }
}

struct ContentView : View {
    
    @State var isMessageVisible: Bool = false
    @State var text = Text("SwiftUI Transition Animation")
                          .font(.largeTitle)
                          .foregroundColor(.yellow)
    
    var body: some View {
        ZStack {
            Color.indigo.ignoresSafeArea()
            
            VStack {
                Spacer()
                Button("SHOW MY MESSAGE") {
                    withAnimation(.linear(duration: 2)) {
                        isMessageVisible.toggle()
                    }
                }
            }
            if isMessageVisible {
                text.transition(.inOpacity)       // in
            } else {
                text.transition(.outOpacity)      // out
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.