Create an animated transition with Matched Geometry Effect in SwiftUI

Create an animated transition with Matched Geometry Effect in SwiftUI

Learn how to use matched geometry effect to animate views in SwiftUI

In SwiftUI we can create smooth transitions between views from one state to another with the Matched Geometry Effect. Using unique identifiers we can blend the geometry of two views with the same identifier creating an animated transition. Transitions like this can be useful for navigation or changing the state of UI elements.

Example of an animation that can be achieved using the Matched Geometry Effect - App Store by Apple

To implement it on your user interface you must:

  1. Define the namespace that will be used to synchronize the geometry of the views;
  2. Define the initial and final states of the views that will be animated;
  3. Use the proper view modifier to identify the initial and final states for the matched geometry transition to take effect;
  4. Trigger the transition.

Let’s add an animated transition using the Matching Geometry Effect on the following view:

struct AnimatedExampleView: View {
    // Variable to trigger the animated transition
    @State var isExpanded: Bool = true
    
    var body: some View {
        VStack {
            if isExpanded {
                smallSizeView()
            } else {
                largeSizeView()
            }
        }
        .padding()
        // On tap the transition is triggered
        .onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
    
    @ViewBuilder
    func smallSizeView() -> some View {
        // Initial state of the view
        RoundedRectangle(cornerRadius: 25)
            .fill(.black)
            .frame(width: 300,height: 300)
            .overlay {
                Text("Hello Developer")
                    .font(.headline)
                    .foregroundStyle(.white)
            }
    }
    
    @ViewBuilder
    func largeSizeView() -> some View {
        // Final state of the view
        RoundedRectangle(cornerRadius: 25)
            .fill(.black)
            .overlay {
                Text("Hello Developer")
                    .font(.headline)
                    .foregroundStyle(.white)
            }
    }
    
}

To enable the Matching Geometry Effect you need to:

struct AnimatedExampleView: View {
    @State var isExpanded: Bool = true
    
    // Identifier for the rectangle view
    private var rectangleId = "Rectangle"
    
    // Namespace for the expansion effect
    @Namespace var expansionAnimation
    
    var body: some View {
        VStack {
            if isExpanded {
                smallSizeView()
            } else {
                largeSizeView()
            }
        }
        .padding()
        .onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
    
    @ViewBuilder
    func smallSizeView() -> some View {
        RoundedRectangle(cornerRadius: 25)
            .fill(.black)
            .frame(width: 300,height: 300)
            // Added the matched geometry modifier to the view
            .matchedGeometryEffect(id: rectangleId, in: expansionAnimation)
            .overlay {
                Text("Hello Developer")
                    .font(.headline)
                    .foregroundStyle(.white)
            }
    }
    
    @ViewBuilder
    func largeSizeView() -> some View {
        RoundedRectangle(cornerRadius: 25)
            .fill(.black)
            // Added the matched geometry modifier to the view
            .matchedGeometryEffect(id: rectangleId, in: expansionAnimation)
            .overlay {
                Text("Hello Developer")
                    .font(.headline)
                    .foregroundStyle(.white)
            }
    }
    
}

With that done, when the animation is triggered the views that have the same identifier and are in the same namespace will seamlessly transition.