MatchedGeometryEffect SwiftUI

Hello

I am using matched Geometry Effect to make animations and transitions, the problem is that when I press to start the animation, the object being animated, in this case Text, is duplicated during the transition, and then when I press again to get it back to its original position, no animation takes place, how can I fix it.

Here is the code:

struct ContentView: View {
    
    @StateObject var numberViewModel = NumberViewModel()
    
    @Namespace var animation
    
    var body: some View {
        GeometryReader { geo in
            NavigationView{
                ZStack {
                    ScrollView{
                        LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())]) {
                            ForEach(numbers){ number in
                                NumberView(numberViewModel: numberViewModel, animation: animation, number: number)
                                    .onTapGesture {
                                        withAnimation(.easeInOut(duration: 1)){
                                            numberViewModel.selected = number
                                            numberViewModel.tapped = true
                                        }
                                    }
                            }
                        }
                    }
                    if numberViewModel.tapped{
                    NumberTappedView(animation: animation, numberViewModel: numberViewModel)
                        .position(
                            x: geo.frame(in:.global).midX,
                            y: geo.frame(in:.global).midY
                        )
                        .onTapGesture {
                            withAnimation(.easeInOut(duration: 1)){
                                numberViewModel.selected = Number(number: 0)
                                numberViewModel.tapped = false
                            }
                        }
                    }
                }
            }
        }
    }
}

struct NumberView: View {
    
    @ObservedObject var numberViewModel: NumberViewModel
    
    var animation: Namespace.ID
    
    var number: Number
    
    var body: some View{
        GroupBox{
            if !(numberViewModel.selected.number == number.number){
                Text("\(number.number)")
                    .font(.largeTitle)
                    .frame(width: 100, height: 100, alignment: .center)
                    .matchedGeometryEffect(id: number.number, in: animation)
            }
        }
    }
}

struct Number: Identifiable {
    var id = UUID()
    var number: Int
}

var numbers: [Number] = [
    Number(number: 1),
    Number(number: 2)
]

struct NumberTappedView: View {
    var animation: Namespace.ID
    
    @ObservedObject var numberViewModel: NumberViewModel
    
    var body: some View{
        GroupBox{
            Text("\(numberViewModel.selected.number)")
                .font(.largeTitle)
                .frame(width: 200, height: 200, alignment: .center)
                .matchedGeometryEffect(id: numberViewModel.selected.number, in: animation)
                
        }
    }
}



class NumberViewModel: ObservableObject {
    @Published var selected: Number = Number(number: 0)
    @Published var tapped: Bool = false
}

Thank You!

Answered by OOPer in 686429022

I have not played with matchedGeometryEffect yet, so read the followings as as far as I tried things. There may be other better ways.

Text, is duplicated during the transition

You have two Texts, and with using matchedGeometryEffect, they both are animated. Applying matchedGeometryEffect after frame is specified, the positions of the two in animation are different. Please try moving matchedGeometryEffect before frame.

when I press again to get it back to its original position, no animation takes place

Seems some sort of symmetry is needed to trigger animation when you set numberViewModel.tapped to false.

Please try something like this:

struct NumberView: View {
    
    @ObservedObject var numberViewModel: NumberViewModel
    var animation: Namespace.ID
    var number: Number
    
    var body: some View{
        GroupBox{
            if numberViewModel.selected.number != number.number {
                Text("\(number.number)")
                    .font(.largeTitle)
                    .matchedGeometryEffect(id: number.number, in: animation) //<-
                    .frame(width: 100, height: 100, alignment: .center)
            }
        }
    }
}

struct NumberTappedView: View {
    
    var animation: Namespace.ID
    @ObservedObject var numberViewModel: NumberViewModel
    
    var body: some View{
        GroupBox {
            if numberViewModel.tapped { //<-
                Text("\(numberViewModel.selected.number)")
                    .font(.largeTitle)
                    .matchedGeometryEffect(id: numberViewModel.selected.number, in: animation) //<-
                    .frame(width: 200, height: 200, alignment: .center)
            } //<-
        }
    }
}

Or you would prefer this version of NumberTappedView:

struct NumberTappedView3: View {
    
    var animation: Namespace.ID
    @ObservedObject var numberViewModel: NumberViewModel
    
    var body: some View{
        GroupBox {
            if numberViewModel.tapped {
                ZStack {
                    ForEach(numbers) { number in
                        if numberViewModel.selected.number == number.number {
                            Text("\(numberViewModel.selected.number)")
                                .font(.largeTitle)
                                .matchedGeometryEffect(id: numberViewModel.selected.number, in: animation)
                                .frame(width: 200, height: 200, alignment: .center)
                        }
                    }
                }
            }
        }
    }
}
Accepted Answer

I have not played with matchedGeometryEffect yet, so read the followings as as far as I tried things. There may be other better ways.

Text, is duplicated during the transition

You have two Texts, and with using matchedGeometryEffect, they both are animated. Applying matchedGeometryEffect after frame is specified, the positions of the two in animation are different. Please try moving matchedGeometryEffect before frame.

when I press again to get it back to its original position, no animation takes place

Seems some sort of symmetry is needed to trigger animation when you set numberViewModel.tapped to false.

Please try something like this:

struct NumberView: View {
    
    @ObservedObject var numberViewModel: NumberViewModel
    var animation: Namespace.ID
    var number: Number
    
    var body: some View{
        GroupBox{
            if numberViewModel.selected.number != number.number {
                Text("\(number.number)")
                    .font(.largeTitle)
                    .matchedGeometryEffect(id: number.number, in: animation) //<-
                    .frame(width: 100, height: 100, alignment: .center)
            }
        }
    }
}

struct NumberTappedView: View {
    
    var animation: Namespace.ID
    @ObservedObject var numberViewModel: NumberViewModel
    
    var body: some View{
        GroupBox {
            if numberViewModel.tapped { //<-
                Text("\(numberViewModel.selected.number)")
                    .font(.largeTitle)
                    .matchedGeometryEffect(id: numberViewModel.selected.number, in: animation) //<-
                    .frame(width: 200, height: 200, alignment: .center)
            } //<-
        }
    }
}

Or you would prefer this version of NumberTappedView:

struct NumberTappedView3: View {
    
    var animation: Namespace.ID
    @ObservedObject var numberViewModel: NumberViewModel
    
    var body: some View{
        GroupBox {
            if numberViewModel.tapped {
                ZStack {
                    ForEach(numbers) { number in
                        if numberViewModel.selected.number == number.number {
                            Text("\(numberViewModel.selected.number)")
                                .font(.largeTitle)
                                .matchedGeometryEffect(id: numberViewModel.selected.number, in: animation)
                                .frame(width: 200, height: 200, alignment: .center)
                        }
                    }
                }
            }
        }
    }
}
MatchedGeometryEffect SwiftUI
 
 
Q