SwiftUI - 动画 ZStack 大小时未对齐的矩形

SwiftUI - Misaligned Rectangles when Animating Size of ZStack

我正在 SwiftUI 中创建一个自定义的“披露组”,它实现了一种“滑动删除”功能。它的工作原理是将两个矩形堆叠在一起,底部的矩形是滑动时看到的红色“删除”按钮。我还有一个布尔值,用于标记组件是否“扩展”,即具有更大的尺寸。这是一段视频:

如您所见,该组件在点击时会变大,在拖动时会显示一个红色的“删除”按钮。但是,展开组件时,你可以看到底部显示了删除矩形的一部分。下面是实现,我不确定为什么两个矩形不完全聚在一起。有谁知道我怎样才能避免这个故障?

MRE:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 30) {
            TestDisclosure()
            TestDisclosure()
            TestDisclosure()
            Spacer()
        }
    }
}

struct TestDisclosure: View {
    @State var expanded: Bool = false
    @State var isDeleting: Bool = false
    @State var horzdrag: CGFloat = 0 // the horizontal translation of the drag
    @State var predictedEnd: CGFloat = 0 // the predicted end translation of the drag

    var body: some View {
        ZStack {
            Rectangle()
                .foregroundColor(.red)

            label
                .clipped()
                .offset(x: getOffset(horzdrag: horzdrag))
                .animation(.spring(), value: horzdrag)
        }
        .offset(x: isDeleting ? -400 : 0)
        .animation(.spring(), value: isDeleting)
        .transition(.move(edge: .leading))
        .gesture(
            DragGesture()
                .onChanged { gesture in
                    onDragChange(gesture: gesture)
                }
                .onEnded { _ in
                    onDragEnd()
                }
        )
        .cornerRadius(15)
        .padding(.horizontal)
        .onTapGesture {
            withAnimation(.spring()) {
                expanded.toggle()
            }
        }
        .frame(maxHeight: expanded ? 150 : 85)
        .clipped()
    }

    private var label: some View {
        ZStack {
            Rectangle()
                .foregroundColor(.teal)

            VStack {
                HStack {
                    Text("Test")
                    Spacer()
                    Text("1 unit")
                    Text("12 units")
                }
                .padding(.horizontal)
            }
        }
    }

    private func onDragChange(gesture: DragGesture.Value) {
        horzdrag = gesture.translation.width
        predictedEnd = gesture.predictedEndTranslation.width
    }

    private func onDragEnd() {    
        if getOffset(horzdrag: horzdrag) <= -400 {
            withAnimation(.spring()) {
                isDeleting = true
            }
        }

        horzdrag = .zero
    }

    // used to calculate how far to move the teal rectangle
    private func getOffset(horzdrag: CGFloat) -> CGFloat {
        if isDeleting {
            return -400
        } else if horzdrag < -165 {
            return -400
        } else if predictedEnd < -60 && horzdrag == 0 {
            return -80
        } else if predictedEnd < -60 {
            return horzdrag
        } else if predictedEnd < 50 && horzdrag > 0 && (-80 + horzdrag <= 0) {
            return -80 + horzdrag
        } else if horzdrag < 0 {
            return horzdrag
        } else {
            return 0
        }
    }
}

这看起来像是同一动画(spring 在这种情况下)应用于不同属性的干扰,`因为在拖动(偏移)尚未完成时应用点击(折叠)时观察到效果。

我不确定这是否是一个错误,但解决方法是使用不同类型的动画。

这是一个解决方法 - 使用 default 而不是 spring。 使用 Xcode 13.3 / iOS 15.4

测试
label
    .clipped()
    .offset(x: getOffset(horzdrag: horzdrag))
    .animation(.default, value: horzdrag)     // << here !!