1

I use matchedGeometryEffect and TapGesture to swap two elements with animated transition.

The animation works as expected but I want to change the zIndex of the elements for the duration of the animation.

I started from this example : https://www.appcoda.com/matchedgeometryeffect/
and adapted it to my needs :

struct MyView: View {
    @State var colors = [Color.orange,Color.red,Color.yellow,Color.pink,Color.blue]
    @State var target = Int.random(in: 0..<4)
    @Namespace private var animationNamespace : Namespace.ID
    
    var body: some View {
        let columns: [GridItem] = Array(repeating: .init(.fixed(80)), count: 2)
        LazyVGrid(columns: columns, spacing: 5){
            ForEach(Array(self.colors.enumerated()), id: \.self.element) { indexedColor in
                Element(color: indexedColor.element, animationNamespace: self.animationNamespace, action: {
                    self.colors.swapAt(target, indexedColor.offset)
                    target = Int.random(in: 0..<4)
                })
            }
        }.animation(.linear)
    }
}

struct Element: View {
    let color : Color
    let animationNamespace : Namespace.ID
    let action: () -> Void

    
    var body: some View {
        Circle()
            .fill(color)
            .frame(width: 80, height: 80)
            .matchedGeometryEffect(id: color, in: animationNamespace)
            .onTapGesture {
                action()
            }
    }
}

animation

I want the animated elements to be always on top of the other elements.
The zIndex must be reseted at the end of the animation

Is this possible by using TapGesture ?

Xcode Version 12.0.1 (12A7300)
iOS 14.0

fdelsert
  • 768
  • 1
  • 10
  • 22

1 Answers1

0

It is possible to put the clicked dot on the top level.. I changed your code here..

struct ContentView: View {
    @State var colors = [Color.orange,Color.red,Color.yellow,Color.pink,Color.blue]
    @Namespace private var animationNamespace : Namespace.ID
    
    var body: some View {
        HStack{
            ForEach(Array(self.colors.enumerated()), id: \.self.element) { indexedColor in
                Element(color: indexedColor.element, animationNamespace: self.animationNamespace, order: indexedColor.0, action: {
                    self.colors.swapAt(0, indexedColor.offset)
                })
            }
        }.animation(.linear)
    }
}

struct Element: View {
    let color : Color
    let animationNamespace : Namespace.ID
    var order : Int
    let action: () -> Void
    
    var body: some View {
        Circle()
            .fill(color)
            .frame(width: 80, height: 80)
            .matchedGeometryEffect(id: color, in: animationNamespace)
            .zIndex(Double(5-order))
            .onTapGesture {
                action()
            }
    }
}

Basically I just put the zIndex for each element based on their position. This means the first dot will have the highest zIndex. If you change the positions, the clicked one will get the highest zIndex and will lay on top of each other. I think it is not possible to put the other one on top aswell, as it will get the other zIndex, which was the lower one.

davidev
  • 7,694
  • 5
  • 21
  • 56
  • 1
    I apologize, I did not provide a reproducible example. My case is more complex, my elements are dynamic and I don't know in advance which circle will be chosen. I will edit my question. Feel free to delete or complete your answer. – fdelsert Oct 31 '20 at 15:28
  • I have updated the code, so the clicked dot will always be on top of each others – davidev Oct 31 '20 at 15:52
  • 1
    Nice hack ! It's work because the target index is always zero . It's not my case. I update my code with a randomly generated target index. Also in my real code the container is a LazyVStack. – fdelsert Oct 31 '20 at 16:07