SwiftUI 动画:一些隐式过渡动画在 iOS 13 上被破坏了?

Posted

技术标签:

【中文标题】SwiftUI 动画:一些隐式过渡动画在 iOS 13 上被破坏了?【英文标题】:SwiftUI animation: Some implicit transition animations broken on iOS 13? 【发布时间】:2020-03-31 20:45:13 【问题描述】:

在 Xcode 11.3.1 和 11.4 上测试:

将隐式动画附加到过渡时,某些过渡类型似乎被破坏了。具体来说,任何与位置相关的转换都不会应用给定的隐式动画。 .slide.move.offset 坏了。 .opacity.scale 似乎还可以。 (见附件)

显式动画似乎在所有情况下都可以正常工作。

即使使用自定义复合过渡,与位置相关的子过渡也不响应隐式动画。

这是错误还是预期行为?

如果您想基于隐式属性更改为特定 UI 元素触发多个不同的动画曲线,这似乎是个问题。


struct MyExample: View 

    @State private var isShowing = true

    private let myAnimation = Animation.spring(response: 0.8, dampingFraction: 0.2, blendDuration: 3.0)
    var body: some View 
        VStack(spacing:20) 
            if self.isShowing 
                Text("Opacity").modifier(MyBigFont())
                    .transition(AnyTransition.opacity.animation(myAnimation))

                Text("Scale").modifier(MyBigFont())
                    .transition(AnyTransition.scale.animation(myAnimation))

                Text("Slide").modifier(MyBigFont())
                    .transition(AnyTransition.slide.animation(myAnimation))

                Text("Move").modifier(MyBigFont())
                    .transition(AnyTransition.move(edge: .trailing).animation(myAnimation))

                Text("Offset").modifier(MyBigFont())
                    .transition(AnyTransition.offset(x: 20, y: 0).animation(myAnimation))

                Text("Custom").modifier(MyBigFont())
                    .transition(AnyTransition.myCustomTransition.animation(myAnimation))
            

            Spacer()

            Button(action: 
                self.isShowing.toggle()
            ) 
                Text("Implicit Toggle")
            

            Button(action: 
                withAnimation(self.myAnimation) 
                    self.isShowing.toggle()
                
            ) 
                Text("Explicit Toggle")
            
        
    



struct MyBigFont: ViewModifier 
    func body(content: Content) -> some View 
        content
            .lineLimit(1)
            .padding()
            .background(Color.purple)
            .foregroundColor(.white)
            .cornerRadius(8)
            .font(Font.system(size: 21).bold())
    


struct MyCustomTransition: ViewModifier 

    var isEnabled: Bool

    func body(content: Content) -> some View 

        if isEnabled 
            return content
                .offset(x: 20.0, y: 0.0)
                .opacity(0)
         else 
            return content
                .offset(x: 0.0, y: 0.0)
                .opacity(1)
        

    


extension AnyTransition 
    static let myCustomTransition = AnyTransition.modifier(
        active: MyCustomTransition(isEnabled: true),
        identity: MyCustomTransition(isEnabled: false))


struct ContentView_Previews: PreviewProvider 
    static var previews: some View 
        VStack 
            MyExample()
            Spacer()
        
    


【问题讨论】:

【参考方案1】:

根据 Javier 的说法,从 Xcode 11.2 开始,过渡的隐式动画不再起作用

如果有人有更新的信息,请回复。

请注意,从 XCode 11.2 开始,过渡不再适用于隐式动画。

https://swiftui-lab.com/advanced-transitions/

【讨论】:

【参考方案2】:

要使隐式转换正确动画化,需要制作包含这些转换的动画容器。

使用 Xcode 11.4 / ios 13.4 测试。

这是唯一的修复:

struct MyExample: View 

    @State private var isShowing = true

    private let myAnimation = Animation.spring(response: 0.8, dampingFraction: 0.2, blendDuration: 3.0)
    var body: some View 
        VStack(spacing:20) 
            if self.isShowing 

                ...      // all your code here

            
        .animation(myAnimation)    // << fix !!
    

【讨论】:

但这不会触发附加到每个过渡的隐式动画。它用显式动画覆盖它们。这与使用单个 withAnimation 包装属性更改的效果相同【参考方案3】:

Asperi 的回答是完全正确的,刚刚用 Xcode 13.2.1 和 iOS 15.2 / macOS 12.2 测试了它,下面这个简单的例子:

struct ContentView: View 
    @State private var show = false
    
    var body: some View 
        VStack(spacing: 20) 
            roundedRect(color: .blue)
            if show 
                roundedRect(color: .red)
                    .transition(.opacity.animation(.easeInOut))
            
            
            Spacer()
            
            Button(show ? "Hide" : "Show") 
                show.toggle()
            
        
        .animation(.easeInOut)
        .padding()
        .frame(width: 190, height: 420, alignment: .top)
    

令人惊讶的是,这有效:

Opacity transition with implicit animation

然后我尝试了相同的设置,只是使用 .slide 转换,然后它停止工作:

Slide transition with implicit animation

然后我尝试将动画移到下一个容器(VStack),就像上面建议的 Asperi 一样:

struct ContentView: View 
    @State private var show = false
    
    var body: some View 
        VStack(spacing: 20) 
            roundedRect(color: .blue)
            if show 
                roundedRect(color: .red)
                    .transition(.slide)
            
            
            Spacer()
            
            Button(show ? "Hide" : "Show") 
                show.toggle()
            
        
        .animation(.easeInOut)
        .padding()
        .frame(width: 190, height: 420, alignment: .top)
    

瞧,转场又开始动画了。

Slide transition with implicit animation on the containing VStack

【讨论】:

是的,这是为单个视图设置动画的解决方法。但看看我对 Asperi 的回答的评论。当您有多个子视图,每个子视图都想要自己不同的隐式动画时,它并不能解决问题。 (就像我原来的例子) 不,不是。在您最初的问题中,您为每个视图(.animation(myAnimation))使用相同的动画,只是不同的过渡。因此,如果您保持 Text 上的过渡,但将 .animation(myAnimation) 移动到包含 VStack 上,它应该可以工作。 我的问题是关于每个元素触发不同的隐式过渡和动画。据我所知,没有解决方法。已接受答案中的链接似乎仍然正确(隐式转换不起作用)。 这并没有提供问题的答案。一旦你有足够的reputation,你就可以comment on any post;相反,provide answers that don't require clarification from the asker。 - From Review

以上是关于SwiftUI 动画:一些隐式过渡动画在 iOS 13 上被破坏了?的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI4.0有趣的动画升级:新iOS16视图内容过渡动画

滚动视图中的 SwiftUI 动画/过渡表现奇怪

在 SwiftUI 中将视图添加到层​​次结构时如何动画过渡

SwiftUI:动画过渡

如何在 SwiftUI 中动画/过渡文本值更改

SwiftUI - 动画过渡