I'm familiar with how matched geometry works on normal objects or elements but it becomes glitchy and confusing when we focus on images. The code below is as far as I've gotten. It performs well when swiping out of the image but the issue is that I need the image cropped (using clipped()) for the specified frame. When i clip it, there's a glitch in the animation (toggling showing).
import SwiftUI
struct View: View {
@Namespace var nm
@State var showing = false
@State private var offset: CGSize = .zero
var body: some View {
GeometryReader { geo in
ZStack {
if !showing {
VStack {
HStack {
Spacer()
Image("im1")
.resizable()
.matchedGeometryEffect(id: "im", in: nm)
.aspectRatio(contentMode: .fill)
.containerRelativeFrame(.horizontal) { size, axis in
size * 0.224
}
.containerRelativeFrame(.vertical) { size, axis in
size * 0.111
}
.clipped()
.onTapGesture {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
showing = true
}
}
Spacer()
}
}
.padding(.top)
} else {
VStack {
Spacer()
Image("im1")
.resizable()
.matchedGeometryEffect(id: "im", in: nm)
.aspectRatio(contentMode: .fit)
.onTapGesture {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
showing = false
}
}
.offset(x: offset.width, y: offset.height)
.gesture (
DragGesture()
.onChanged { value in
if value.translation.height > 0 {
withAnimation(.spring(response: 0.1, dampingFraction: 0.95)) {
offset = value.translation
}
}
}
.onEnded { value in
if offset.height > 100 {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)){
offset = .zero
showing = false
}
} else {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)){
offset = .zero
}
}
}
)
Spacer()
}
}
}
}
}
}
I've tried alternating between normal frame() and this containerRelativeFrame and they seem to be producing the same result. I've also tried changing the aspectRatio but still has not worked.
I am placing the .clipped() after the frame is set on the image.
To be clear, the animation still works. I just notice that you can see the border of the clipped image before it fills the space. If you were to play the animation slowly you would see what I mean.
The way you are using
.matchedGeometryEffect
is to perform a transition between two images with different sizes and different aspect ratios. Due to the different aspect ratios, it is noticeable how one image fades out while the other fades in.To avoid the fade-out/fade-in, you could try using a single image which is moving between invisible placeholders:
Color.clear
. They are used as the source for the.matchedGeometryEffect
, defining the size and position for the image.ZStack
, withisSource: false
.GeometryReader
that you had around theZStack
was redundant, so it can be dropped. However, theZStack
needsalignment: .top
to work in the same way..clipped()
doesn't work when the frame is being applied by.matchedGeometryEffect
. As a workaround, the image can be shown as an overlay overColor.clear
. This combination then needs to be clipped before applyingmatchedGeometryEffect
.