Swift UI matched geometry effect with clip

38 Views Asked by At

I would like to execute a matched geometry effect. The initial view is clipped to either a rectangle or circle, and the overlay view is not clipped at all. How can I modify the matched geometry effect so that it smoothly transitions from a clipped view to no clip?

struct ContentView: View {
    @State private var show = false
    @Namespace var namespace
    
    var body: some View {
        VStack {
            if !show {
                KFImage(URL(string: "https://dfstudio-d420.kxcdn.com/wordpress/wp-content/uploads/2019/06/digital_camera_photo-1080x675.jpg"))
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .matchedGeometryEffect(id: "squareAnim", in: namespace)
                    .frame(width: 37.5, height: 37.5)
                    .clipShape(Circle())
                    .onTapGesture {
                        withAnimation {
                            show.toggle()
                        }
                    }
            }
        }
        .overlay {
            if show {
                KFImage(URL(string: "https://dfstudio-d420.kxcdn.com/wordpress/wp-content/uploads/2019/06/digital_camera_photo-1080x675.jpg"))
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .matchedGeometryEffect(id: "squareAnim", in: namespace)
                    .frame(width: 150.0, height: 150.0)
                    .onTapGesture {
                        withAnimation {
                            show.toggle()
                        }
                    }
            }
        }
    }
}
1

There are 1 best solutions below

1
Benzy Neez On BEST ANSWER

You could use the same techniques as shown in the answer to In Swift, how can I matchedGeometryEffect for a clipped() image without it glitching? (it was my answer).

  • Use placeholders in a ZStack to define the two positions.
  • The small version needs to be scaled-to-fill, the larger version needs to be scaled-to-fit.
  • A frame with maxHeight (or maxWidth) can be applied to the ZStack to constrain the size of the large version.
  • One way to apply a round clip shape to the small version, but not to the large version, is to use a RoundedRectangle. In the small size, the corner radius should give a round shape. In the large size, a corner radius of 0 can be used.
  • For the clip to work, the image needs to be shown in an overlay.
  • I would suggest, the animation looks better when .easeInOut is used, instead of the default .spring.
ZStack {
    Color.clear
        .frame(width: 37.5, height: 37.5)
        .matchedGeometryEffect(id: "small", in: namespace, isSource: true)

    Color.clear
        .matchedGeometryEffect(id: "big", in: namespace, isSource: true)

    Color.clear
        .overlay {
            KFImage(URL(string: "https://dfstudio-d420.kxcdn.com/wordpress/wp-content/uploads/2019/06/digital_camera_photo-1080x675.jpg"))
                .resizable()
                .aspectRatio(contentMode: show ? .fit : .fill)
        }
        .clipShape(RoundedRectangle(cornerRadius: show ? 0 : 18.75))
        .matchedGeometryEffect(
            id: show ? "big" : "small",
            in: namespace,
            isSource: false
        )
        .onTapGesture {
            withAnimation(.easeInOut) {
                show.toggle()
            }
        }
}
.frame(maxHeight: 150)

Animation