通过 MatchedGeometryEffect 向上滑动视图

Sliding view up by MatchedGeometryEffect

我想使用 .matchedGeometryEffect 实现图像被点击时的向上滑动动画。我只想用它来动画位置变化,因为两个图像的帧是相同的。在此示例中,我将图像替换为黑色矩形,因为它也可以重现。 简化代码:

struct ContentView: View {
    @Namespace var namespace
    @State var isShowingDetail = false
    var body: some View {
            if isShowingDetail {
                VStack {
                    Rectangle()
                    .foregroundColor(.black)
                    .matchedGeometryEffect(id: "image", in: namespace, properties: .position)
                    .aspectRatio(contentMode: .fit)
                    .edgesIgnoringSafeArea(.top)
                    Spacer()
                }
                .onTapGesture {
                    changeView()
                }
            } else {
                Rectangle()
                    .foregroundColor(.black)
                    .matchedGeometryEffect(id: "image", in: namespace, properties: .position)
                    .aspectRatio(contentMode: .fit)
                    .cornerRadius(8)
                    .onTapGesture {
                        changeView()
                    }
            }
    }

    private func changeView() {
        withAnimation(.easeOut(duration: 0.3)){
            isShowingDetail.toggle()
        }
    }
}

视图向上滑动,但它没有正确设置动画,因为它有淡入淡出的效果。我假设它与应用于每个视图的默认淡入淡出过渡有关,我试图设置一个自定义的,但找不到适合这种情况的。

修饰符的位置很重要,对于 matchedGeometryEffect 来说更重要。

这里是固定变体。 Xcode 13.2 / iOS 15.2

var body: some View {
    if isShowingDetail {
        VStack {
            Rectangle()
                .foregroundColor(.black)
                .aspectRatio(contentMode: .fit)
                .edgesIgnoringSafeArea(.top)
                                            // !! applied here !!
                .matchedGeometryEffect(id: "image", in: namespace, properties: .position)
            Spacer()
        }
        .onTapGesture {
            changeView()
        }
    } else {
        Rectangle()
            .foregroundColor(.black)
            .aspectRatio(contentMode: .fit)
            .cornerRadius(8)
                               // !! applied here !!
            .matchedGeometryEffect(id: "image", in: namespace, properties: .position)
            .onTapGesture {
                changeView()
            }
    }
}

我想通了 blink/fade 效果移除部分。如我所料,它与在没有显式过渡时适用的默认淡入淡出过渡有关。我为这种情况找到了合适的:

            if isShowingDetail {
                VStack {
                    Rectangle()
                    .foregroundColor(.black)
                    .aspectRatio(contentMode: .fit)
                    .matchedGeometryEffect(id: "image", in: namespace, properties: .position)
                    .edgesIgnoringSafeArea(.top)
                    Spacer()
                }
                .transition(.offset())  // applied here
                    .onTapGesture {
                        changeView()
                    }
            } else {
                Rectangle()
                    .foregroundColor(.black)
                    .aspectRatio(contentMode: .fit)
                    .matchedGeometryEffect(id: "image", in: namespace, properties: .position)
                    .transition(.offset())  // applied here
                    .onTapGesture {
                        changeView()
                    }
            }