swift - 如何在 SwiftUI 中保持过渡动画相同的标识

标签 swift swiftui swiftui-animation

我正在尝试实现 SwiftUI View 的两种状态之间的平滑过渡。以下代码效果很好,当偏移量发生变化时,SwiftUI 将其解释为同一 View (相同标识)的 2 个状态,并通过 View 的幻灯片进行转换。

import SwiftUI

struct AnimationView: View {

    @State var number: Int = 0

    var body: some View {
        VStack {
            // VIEW #1
            Image(systemName: "0.circle.fill")
            // VIEW #2
//             Image(systemName: "\(self.number).circle.fill")
                .id("my-view-identity")
                .offset(x: self.number%2==0 ? 50 : -50, y: 0)
            Button {
                self.number += 1
            } label: {
                Text("Add 1")
            }
        }
        .animation(.linear(duration: 1), value: self.number)
    }
}

struct AnimationView_Previews: PreviewProvider {
    static var previews: some View {
        AnimationView()
    }
}

问题是当我尝试使用 VIEW #2 时,SwiftUI 无法解释 2 个状态 Image(systemName: "0.circle.fill")Image(systemName: “1.circle.fill”) 作为同一 View 的 2 个状态,而是具有不同身份的 2 个不同 View ,即使我手动设置了 id。这会导致第一个 View 淡出,第二个 View 淡入。

我还尝试使用 subview 创建某种 View 代理并将固定 id 设置为该 View ,但这没有做任何事情。

我看过 WWDC21 演讲 Demystify SwiftUI 并且认为我对 View 标识有很好的掌握,但是这个我无法理解,我的猜测是我们可以为单个 View 手动设置不同的 id,但是我们不能做相反的事情:为不同的 View 设置相同的 id。我对吗 ?有什么技巧可以实现这一点吗?

最佳答案

方法1:ZStack.opacity()

这个技巧有效。将所有 ImageView 放入 ZStack 中,并使用 .opacity() 仅使当前 ImageView 可见:

struct AnimationView: View {
    
    @State var number: Int = 0
    
    var body: some View {
        VStack {
            ZStack {
                ForEach(0..<51) { num in
                    Image(systemName: "\(num).circle.fill")
                        .opacity(self.number == num ? 1 : 0)
                }
            }
            .offset(x: self.number % 2 == 0 ? 50 : -50, y: 0)
            Button {
                self.number += 1
            } label: {
                Text("Add 1")
            }
        }
        .animation(.linear(duration: 1), value: self.number)
    }
}

这是有效的,因为所有 View 都在屏幕上(只是不可见),因此当您移动它们时,它们不会被视为新 View ,而是刚刚更改了不透明度的 View 。这保留了对象标识并允许移动动画正常工作。


方法2:.id().matchedGeometryEffect()

为 View 分配一个.id(self.number)来区分 View ,并使用.matchedGeometryEffect()来统一动画的 View 。

struct AnimationView: View {
    
    @State var number: Int = 0
    @Namespace var animation
    
    var body: some View {
        VStack {
            Image(systemName: "\(self.number).circle.fill")
                .matchedGeometryEffect(id: "id1", in: animation)
                .offset(x: self.number % 2 == 0 ? 50 : -50, y: 0)
                .id(self.number)
            Button {
                self.number += 1
            } label: {
                Text("Add 1")
            }
        }
        .animation(.linear(duration: 1), value: self.number)
    }
}

View 修饰符的顺序在这里至关重要。 .id() 需要位于.offset()之后才能正常工作。


在这两种方法中,我更喜欢方法 2,因为它不会创建 50 个额外的 View !

关于swift - 如何在 SwiftUI 中保持过渡动画相同的标识,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76288452/

相关文章:

swift - 返回 Self 的协议(protocol)函数

ios - 我怎样才能为 sizeForItemAt 设置一个 Collection View ?

ios - SwiftUI 中的 ObservedObject 和 StateObject 有什么区别

ios - 如何修复 Xcode 版本 11.0 测试版 Canvas 崩溃?

animation - SwiftUI 五彩纸屑动画

ios - 更新值 Firebase Swift 3

swift - 何时使用通用参数子句

swift - UIColor 深色模式向后兼容

swiftui - 如何使打字机动画更流畅、不那么模糊?

swiftui - 带有 bool 标志的淡入/淡出动画