I’m trying to use the matched geometry effect on a list and it is working like expected at least for the animation to the detail view.
The problem is that the animation back from the detail view to list list cell seems to be behind the list.
How can I get this working properly so that the detail view will shrink back to a list cell?
Here’s the code for my cell:
struct Cell: View {
var exercise: Exercise
var index: Int
var namespace: Namespace.ID
var body: some View {
VStack {
Text(exercise.title)
.matchedGeometryEffect(id: exercise.id, in: namespace)
.font(.title)
.foregroundColor(.white)
.frame(maxWidth: .infinity, alignment: .leading)
.frame(height: 100)
}
.padding(.horizontal)
.padding(.top)
.padding(.bottom, 4)
.background(
RoundedRectangle(cornerRadius: 5, style: .continuous)
.matchedGeometryEffect(id: exercise.id + "background", in: namespace)
.background(Color.clear)
.foregroundColor(Color.gray)
)
.clipped()
}
}
This is the list itself:
struct ListView: View {
var testData: [Exercise]
var namespace: Namespace.ID
@Binding var tappedCellIndex: Int?
var body: some View {
ScrollView {
LazyVStack {
ForEach(testData.indices) { index in
Cell(exercise: testData[index], index: index, namespace: namespace)
.onTapGesture {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
self.tappedCellIndex = index
}
}
}
}
}
}
}
This is the Content View:
struct ContentView: View {
let testData = [Exercise(title: "Hallo"), Exercise(title: "Bankdrücken"), Exercise(title: "Squats"), Exercise(title: "Seitheben"), Exercise(title: "Klimmzüge"), Exercise(title: "Bizepscurls"), Exercise(title: "Dips"), Exercise(title: "Aufroller"), Exercise(title: "Muscle"), Exercise(title: "Dragon Flies"), Exercise(title: "Hallo"), Exercise(title: "Bankdrücken"), Exercise(title: "Squats"), Exercise(title: "Seitheben"), Exercise(title: "Klimmzüge"), Exercise(title: "Bizepscurls"), Exercise(title: "Dips"), Exercise(title: "Aufroller"), Exercise(title: "Muscle"), Exercise(title: "Dragon Flies")]
@State var tappedCellIndex: Int? = nil
@Namespace var namespace
var body: some View {
ZStack {
VStack {
ListView(testData: testData, namespace: namespace, tappedCellIndex: $tappedCellIndex)
.opacity(tappedCellIndex == nil ? 1 : 0)
.transition(.scale(scale: 1))
}
if let tappedCellIndex = tappedCellIndex {
DetailView(exercise: testData[tappedCellIndex], selectedIndex: $tappedCellIndex, namespace: namespace)
.transition(.scale(scale: 1))
}
}
}
}
This is the detail view from where it should animate back:
struct DetailView: View {
var exercise: Exercise
@Binding var selectedIndex: Int?
var namespace: Namespace.ID
var body: some View {
VStack(alignment: .leading) {
HStack {
Text(exercise.title)
.matchedGeometryEffect(id: exercise.id, in: namespace)
.font(.title)
.frame(maxWidth: .infinity, alignment: .leading)
.foregroundColor(Color.white)
.padding(.top, 12)
Spacer()
Button {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
selectedIndex = nil
}
} label: {
Text("Klicke hier!")
}
}
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding()
.background(
RoundedRectangle(cornerRadius: 30, style: .continuous)
.matchedGeometryEffect(id: exercise.id + "background", in: namespace)
.foregroundColor(Color.gray)
)
}
}
And finally this is the model:
struct Exercise: Identifiable, Hashable, Equatable {
var id = UUID().uuidString
var title: String
static func == (lhs: Exercise, rhs: Exercise) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
sorry for that much code. I hope I added all that is needed.
btw: I also get the error "Multiple inserted views in matched geometry group". I think that is because I don’t hide the list when displaying the detail view.
I read different opinions on this here on stackoverflow and it seems to me thats its not in every case necessary to hide one view when another one is displayed and I maybe can ignore the error message.
I also cant hide the list because a reload would refresh the list and I will start at the top of the list again so the backward animation also won’t work.
I would appreciate any help
2
Answers
I found a solution for the problem. Just needed to set the zIndex for the DetailView like this..
Another thing that I needed to do is to remove the
.clipped()
from the Cell like ChrisR suggested. I didn't changed the order of the frame / matchedGeometryEffect modifier because that prevents a smooth animation in my case.I would definitely recommend to show an empty placeholder to prevent "multiple inserted views". Also the
.frame(maxWidth, maxHeight)
has to come before the.matchedGeometry
, so the view can shrink or grow in the animation.Here is the amended code, check the comments: