Consider the following example:
struct ContentView: View {
@State var showSplash: Bool = true
@Namespace var animationNamespace
var body: some View {
ZStack {
if showSplash {
GeometryReader { geometry in
AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
image.resizable()
.scaledToFill()
.matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
.transition(.move(edge: .bottom))
.frame(width: geometry.size.width)
.transition(.move(edge: .bottom))
.edgesIgnoringSafeArea(.all)
.clipped()
} placeholder: {
Color.gray
}
}
.onTapGesture {
toggleSplashScreen(false)
}
} else {
ScrollView {
GeometryReader { geometry in
AsyncImage(url: URL(string: "https://picsum.photos/seed/864a5875-6d8b-43d6-8d65-04c5cfb13f3b/1920/1440")) { image in
image
image
.resizable()
.scaledToFill()
.matchedGeometryEffect(id: "SplashImage", in: animationNamespace)
.transition(.move(edge: .bottom))
} placeholder: {
Color.gray
}
.frame(width: geometry.size.width, height: 400)
.clipped()
}
.edgesIgnoringSafeArea(.all)
.onTapGesture {
toggleSplashScreen(true)
}
}
}
}
}
}
With a helper method here:
private extension ContentView {
func toggleSplashScreen(_ toggle: Bool) {
withAnimation(.spring(response: 0.85, dampingFraction: 0.95)) {
showSplash = toggle
}
}
}
This produces:
I noticed two things here that I would like to fix
- The flashing white effect when transitioning between the two states.
- I noticed since we are using
AsyncImage, whenshowSplashchanges the AsyncImages only sometimes hits theplaceholderblock. As a result, the transition becomes really choppy. I tested this with a static image from the assets file and the transition then became smooth. I also tried creating a Caching mechanism on the AsyncImage but still had issues with it hittingplaceholderblock sometimes.
Would love to hear any ideas :) Thanks!

MatchedGeometryeffects are a little more involved than what you have. SwiftUI Lab has a great series of posts on this and advanced animations. I think the problem that you are having is that you are not giving theMatchedGeometrythe information it needs to seamlessly translate between the two views, and so you are getting the behavior you are seeing.MatchedGeometryneeds a way of calculating what the in between transitions should look like that doesn't exist in your code.