How to morph liquid glass view transition

Issue #997

An interesting feature in iOS 26 is the ability to create morph “Liquid Glass” effects, where views with the .glassEffect() modifier can fluidly morph into one another. This is achieved using GlassEffectContainer and the glassEffectID(_:in:) modifier.

To enable morphing, each view inside the GlassEffectContainer needs a unique identifier within a shared namespace. This allows SwiftUI to track the views as they are added or removed, creating a seamless transition from one shape to another. You create a namespace using the @Namespace property wrapper.

When views are added or removed, SwiftUI uses the GlassEffectContainer’s spacing and the views’ geometries to create a natural-looking liquid merge animation between neighboring views.

In the example below, we have up to three icons in an HStack, all within a GlassEffectContainer. Each icon has a unique glassEffectID. Tapping the “Morph” button changes the number of icons, triggering the morphing animation.

Note that for the morphing to work, the distance between each icon’s nearest edges must be less than or equal to the container’s spacing.

import SwiftUI

struct GlassEffectDemoView: View {
    @State private var iconCount: Int = 1
    @Namespace private var namespace


    var body: some View {
        ZStack {
            Image(.p10)
                .resizable()
                .scaledToFill()
                .ignoresSafeArea()
            
            VStack(spacing: 24) {
                GlassEffectContainer(spacing: 40.0) {
                    HStack(spacing: 40.0) {
                        Image(systemName: "sun.max.fill")
                            .frame(width: 80.0, height: 80.0)
                            .font(.system(size: 36))
                            .foregroundStyle(.yellow)
                            .glassEffect()
                            .glassEffectID("sun", in: namespace)


                        if iconCount > 1 {
                            Image(systemName: "moon.fill")
                                .frame(width: 80.0, height: 80.0)
                                .font(.system(size: 36))
                                .foregroundStyle(.gray)
                                .glassEffect()
                                .glassEffectID("moon", in: namespace)
                        }
                        
                        if iconCount > 2 {
                            Image(systemName: "sparkles")
                                .frame(width: 80.0, height: 80.0)
                                .font(.system(size: 36))
                                .foregroundStyle(.purple)
                                .glassEffect()
                                .glassEffectID("sparkles", in: namespace)
                        }
                    }
                }


                Button("Morph") {
                    withAnimation(.bouncy) {
                        iconCount = (iconCount % 3) + 1
                    }
                }
                .buttonStyle(.glass)
            }
        }
    }
}

Read more

Written by

I’m open source contributor, writer, speaker and product maker.

Start the conversation