Home > front end >  Sliding view up by MatchedGeometryEffect
Sliding view up by MatchedGeometryEffect

Time:03-20

I'd like to use .matchedGeometryEffect to achieve a slide up animation for an image when it's tapped. I only want to use this to animate the position change, as the frame is the same for both images. I'm replacing the image with a black rectangle in this example, as it's reproducible with that as well. Simplified code:

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()
        }
    }
}

The the view slides up, but it doesn't animate properly, as it has a fade effect. I assume it's related to the default fade transition applied to every view, I tried to set a custom one couldn't find a proper one for this case.

enter image description here

CodePudding user response:

Location of modifier matters, and for matchedGeometryEffect it is important even more.

Here is fixed variant. Xcode 13.2 / iOS 15.2

demo

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()
            }
    }
}

CodePudding user response:

I figured out the blink/fade effect removal part. As I assuemed, it had to do with the default fade transition that applies if there is no explicit transition. I found a proper one for this case:

            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()
                    }
            }

enter image description here

  • Related