Exploring Morphing Liquid Glass Effects in SwiftUI (iOS 26)

  • Jul 12, 2025

Exploring Morphing Liquid Glass Effects in SwiftUI (iOS 26)

With the introduction of Liquid Glass effects in iOS 26 and beyond, SwiftUI offers a visually rich and fluid way to animate transitions between elements. One of the most compelling features of this system is the ability to morph glassy views between different states using shared identifiers and the new .glassEffectID(_:in:) modifier.

Let’s walk through an example to see how this works in practice and how you can create a seamless, elegant animation using SwiftUI’s GlassEffectContainer and Namespace.

We begin by creating a custom SwiftUI view. At the core of this view is a @State variable called isExpanded, which controls whether a second image appears or disappears during the transition. We are also using the @Namespace property wrapper to coordinate the animation between views using a shared identifier.

import SwiftUI

struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

Inside the body of the view, we add a GlassEffectContainer with a spacing of 60.0. This container ensures that all elements within it support morphing effects. It’s similar in purpose to other SwiftUI containers like ZStack or HStack, but specifically designed to work with the liquid glass visuals introduced with recent iOS design system updates.

struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {
        GlassEffectContainer(spacing: 60.0) {

Within this container, we place an HStack that also uses a spacing of 60.0. SwiftUI uses the spacing value to determine whether views are close enough to morph into each other when their visibility changes.


struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {
        GlassEffectContainer(spacing: 60.0) {
            HStack(spacing: 60.0) {

We begin with a single image: a sun icon using Image(systemName: "sun.max.fill"). This is given a .glassEffect()modifier to apply the shimmering translucent look. Most importantly, it also receives a .glassEffectID("leftEye", in: namespace), which registers it as an animatable glass effect shape in the namespace.

struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {
        GlassEffectContainer(spacing: 60.0) {
            HStack(spacing: 60.0) {
                Image(systemName: "sun.max.fill")
                    .frame(width: 80.0, height: 80.0)
                    .font(.system(size: 36))
                    .glassEffect()
                    .glassEffectID("leftEye", in: namespace)

Next, we use the if isExpanded condition to optionally display a second sun icon. This image is identical in appearance and size to the first, and it receives a different glassEffectID("rightEye", in: namespace). Because these two images share the same parent container and spacing, SwiftUI can smoothly morph between them when the second one appears or disappears.

struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {
        GlassEffectContainer(spacing: 60.0) {
            HStack(spacing: 60.0) {
                Image(systemName: "sun.max.fill")
                    .frame(width: 80.0, height: 80.0)
                    .font(.system(size: 36))
                    .glassEffect()
                    .glassEffectID("leftEye", in: namespace)


                if isExpanded {
                    Image(systemName: "sun.max.fill")
                        .frame(width: 80.0, height: 80.0)
                        .font(.system(size: 36))
                        .glassEffect()
                        .glassEffectID("rightEye", in: namespace)
                }
            }
        }
        .padding(.bottom, 40)

Under the container, we add a button labeled “DevTechie.com” and wrap both GlassEffectContainer and Button into a VStack container. 

struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {
        VStack {
            GlassEffectContainer(spacing: 60.0) {
                HStack(spacing: 60.0) {
                    Image(systemName: "sun.max.fill")
                        .frame(width: 80.0, height: 80.0)
                        .font(.system(size: 36))
                        .glassEffect()
                        .glassEffectID("leftEye", in: namespace)


                    if isExpanded {
                        Image(systemName: "sun.max.fill")
                            .frame(width: 80.0, height: 80.0)
                            .font(.system(size: 36))
                            .glassEffect()
                            .glassEffectID("rightEye", in: namespace)
                    }
                }
            }
            .padding(.bottom, 40)


            Button("DevTechie.com") {

When tapped, this button toggles the isExpanded state with a smooth .easeOut animation lasting five seconds. The length of this animation is intentional—it allows you to clearly see the morphing transition between the single-eye and double-eye views.

import SwiftUI

struct iOS26ExplorationView: View {
    @State private var isExpanded: Bool = false
    @Namespace private var namespace

    var body: some View {
        VStack {
            GlassEffectContainer(spacing: 60.0) {
                HStack(spacing: 60.0) {
                    Image(systemName: "sun.max.fill")
                        .frame(width: 80.0, height: 80.0)
                        .font(.system(size: 36))
                        .glassEffect()
                        .glassEffectID("leftEye", in: namespace)


                    if isExpanded {
                        Image(systemName: "sun.max.fill")
                            .frame(width: 80.0, height: 80.0)
                            .font(.system(size: 36))
                            .glassEffect()
                            .glassEffectID("rightEye", in: namespace)
                    }
                }
            }
            .padding(.bottom, 40)


            Button("DevTechie.com") {
                withAnimation(.easeOut(duration: 5)) {
                    isExpanded.toggle()
                }
            }
            .buttonStyle(.glassProminent)
        }
    }
}

Since the transition is wrapped in withAnimation, SwiftUI handles all the interpolation between shapes, spacing, and opacity internally. What the user sees is a smooth, elegant flow from one configuration to another—thanks to the glassEffectID system keeping track of which shapes are logically connected.

c