Search code examples
animationswiftuiswiftui-matchedgeometryeffect

How to disable the matchedGeometryEffect


I have a view that uses the .matchedGeometryEffect to make moving transitions. I'd like to be able to switch this effect off if the user objects to this type of animation.

I could create two almost identical views, one with the .matchedGeometry modifier and one without, and invoke either of them using an @AppStorage parameter. However, I'm hoping there's a more elegant way of disabling the modifier.

Example code here for matchedGeometry here: SwiftUI: Creating a custom segmented control using .matchedGeometry()

I found a promising article from TwoStraws but I cannot specify .matchedGeometryEffect instead of .linear as a type of animation like this:

Button("Ping pong") {
                var transaction = Transaction(animation: .matchedGeometryEffect)
                transaction.disablesAnimations = true
    
                withTransaction(transaction) {
                    isFlipped.toggle()
                }
            }

Error message:

Type 'Animation?' has no member '_MatchedGeometryEffect'

Is there any other way to disable the .matchedGeometryEffect or am I doomed to write 2 almost identical views?


Solution

  • One way to disable the matched geometry effect is to just pass different IDs to matchedGeometryEffect. You can get unique IDs by doing UUID(). This effectively turns off the matched geometry effect because none of the IDs match each other, but keeps the other animations the same.

    Here is a simple example:

    struct ContentView: View {
        @Namespace var ns
        @State private var toggle = false
        @State private var enabled = true
        var body: some View {
            if toggle {
                Circle().frame(width: 100)
                    // If the *intended* ID is not a UUID, use AnyHashable to erase both of their types
                    .matchedGeometryEffect(id: enabled ? AnyHashable("circle") : AnyHashable(UUID()), in: ns)
            } else {
                Circle().frame(width: 200)
                    .offset(x: 100)
                    .matchedGeometryEffect(id: enabled ? AnyHashable("circle") : AnyHashable(UUID()), in: ns)
            }
            Toggle("Enable Animation", isOn: $enabled)
            Button("Play Animation") {
                withAnimation {
                    toggle.toggle()
                }
            }
        }
    }
    

    Of course, if you don't want any animation, use .animation(nil, value: ...) as you normally would.