SwiftUI VStack 动画看起来坏了 - 动画视图不会改变位置

SwiftUI VStack Animation looks broken - Animated View will not change position

让我们看一下下面的代码片段:

struct ContentView: View {
    @State private var isViewHidden: Bool = false
    let data = [1,2,3,4,5,6,7]
    public var body: some View {
        VStack {
            Button("Hide", action: {
                withAnimation {
                    isViewHidden.toggle()
                }
            })
            ForEach(data, id: \.self) { _ in
                VStack {
                    Text("Foo")
                    if isViewHidden {
                        Text("Bar").animation(nil)
                    }
                }.padding().background(Color.green)
            }
        }
    }
}

我希望 Text("Hide") 会为父 VStack 内的位置设置动画。 但它会坚持到它的最后一个位置并从那里淡出并动画回到那个位置。有没有可能给这个动画一个更自然的感觉,这样它就会在它的父级内部动画。

这是一个可视化问题的 gif:

问题是 Bar 是一个新的 View,因此只是淡出或出现在它的最终目的地,所以它的位置没有动画,因为它之前不在屏幕上。

诀窍是让 Bar 一直显示在屏幕上,只调整它的 opacity.offset 用于防止视图因单词 Bar 并不总是存在而增长。添加或删除空白文本视图 Text(" ") 以使视图像以前一样增长。

以下是提供更好看的动画的解决方法:

struct ContentView: View {
    @State private var isViewHidden: Bool = false
    let data = [1,2,3,4,5,6,7]
    public var body: some View {
        VStack {
            Button("Hide", action: {
                withAnimation {
                    isViewHidden.toggle()
                }
            })
            ForEach(data, id: \.self) { _ in
                VStack {
                    ZStack {
                        Text("Foo")
                        Text("Bar")
                            .offset(y: 28)
                            .opacity(isViewHidden ? 0 : 1)
                    }
                    if !isViewHidden {
                        Text(" ")
                    }

                }.padding().background(Color.green)
            }
        }
    }
}

这里是模拟器中的运行:


方案二:使用.matchedGeometryEffect

这是一个使用 .matchedGeometryEffectText("Bar").frame(height: 0)Text("Bar") 之间设置动画的解决方案。

struct ContentView: View {
    @Namespace var namespace
    @State private var isViewHidden: Bool = false
    let data = [1,2,3,4,5,6,7]
    public var body: some View {
        VStack {
            Button("Hide") {
                withAnimation {
                    isViewHidden.toggle()
                }
            }
            ForEach(data, id: \.self) { id in
                VStack {
                    Text("Foo")
                    if isViewHidden {
                        Text("Bar").frame(height: 0)
                            .matchedGeometryEffect(id: id, in: namespace)
                    } else {
                        Text("Bar")
                            .matchedGeometryEffect(id: id, in: namespace)
                    }
                }
                .padding().background(Color.green)
            }
        }
    }
}