BS
BleepingSwift
Published on

> Coordinating Glass Elements with GlassEffectContainer

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

Placing multiple .glassEffect() views next to each other can produce unexpected results. Glass can't sample other glass, so overlapping glass elements may not blend correctly. SwiftUI solves this with GlassEffectContainer, a container view that coordinates how glass elements render together.

The Problem

When glass elements are close together or overlap without a container, each one renders independently. They don't blend at their edges, and you may see harsh seams or inconsistent lighting:

// Without a container, these render independently
HStack(spacing: 8) {
    Button("Home") { }
        .padding()
        .glassEffect()

    Button("Search") { }
        .padding()
        .glassEffect()

    Button("Profile") { }
        .padding()
        .glassEffect()
}

Using GlassEffectContainer

Wrap your glass elements in a GlassEffectContainer to get proper blending:

import SwiftUI

struct GlassToolbar: View {
    var body: some View {
        GlassEffectContainer {
            HStack(spacing: 8) {
                Button("Home") { }
                    .padding()
                    .glassEffect()

                Button("Search") { }
                    .padding()
                    .glassEffect()

                Button("Profile") { }
                    .padding()
                    .glassEffect()
            }
        }
    }
}

Now the system treats these as a coordinated group. The glass surfaces blend smoothly at their edges, share consistent blur and lighting, and render more efficiently.

Controlling Blend Distance with Spacing

The spacing parameter tells the container how close elements need to be before they visually merge:

// Elements within 30 points will blend together
GlassEffectContainer(spacing: 30) {
    HStack(spacing: 20) {
        Button("Edit") { }
            .padding()
            .glassEffect()

        Button("Share") { }
            .padding()
            .glassEffect()
    }
}

With spacing: 30 and an HStack spacing of 20 points, the buttons are close enough to merge into a unified glass shape. If you increased the HStack spacing beyond 30 points, they'd remain separate.

The spacing parameter is especially useful when you want glass elements that are conceptually grouped to visually connect, while keeping other elements distinct:

GlassEffectContainer(spacing: 40) {
    VStack(spacing: 60) {
        // These two will stay separate (60 > 40)
        HStack(spacing: 16) {
            // But these will blend (16 < 40)
            iconButton("house.fill")
            iconButton("magnifyingglass")
            iconButton("person.fill")
        }

        HStack(spacing: 16) {
            iconButton("gear")
            iconButton("info.circle")
        }
    }
}

Building a Floating Action Menu

Here's a practical example showing a floating action button that expands to reveal options:

import SwiftUI

struct FloatingActionMenu: View {
    @State private var isExpanded = false

    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            Color.blue.gradient
                .ignoresSafeArea()

            GlassEffectContainer(spacing: 20) {
                VStack(spacing: 12) {
                    if isExpanded {
                        actionButton(icon: "camera.fill", label: "Camera")
                        actionButton(icon: "photo.fill", label: "Photos")
                        actionButton(icon: "doc.fill", label: "Files")
                    }

                    Button {
                        withAnimation(.bouncy) {
                            isExpanded.toggle()
                        }
                    } label: {
                        Image(systemName: isExpanded ? "xmark" : "plus")
                            .font(.title2)
                            .fontWeight(.semibold)
                            .foregroundStyle(.white)
                            .frame(width: 56, height: 56)
                    }
                    .glassEffect(.regular.tint(.blue), in: .circle)
                }
            }
            .padding()
        }
    }

    func actionButton(icon: String, label: String) -> some View {
        Button {
            // action
        } label: {
            Label(label, systemImage: icon)
                .foregroundStyle(.white)
        }
        .padding(.horizontal, 16)
        .padding(.vertical, 12)
        .glassEffect()
    }
}

When the menu expands, the buttons appear within the same glass container. Because their spacing (12 points) is less than the container's spacing threshold (20 points), they blend together into a cohesive glass surface as they animate in.

Nested Containers

You can nest GlassEffectContainer views when you need different spacing behavior in different areas:

GlassEffectContainer(spacing: 50) {
    VStack(spacing: 40) {
        // This inner container has its own spacing rules
        GlassEffectContainer(spacing: 16) {
            HStack(spacing: 8) {
                Button("A") { }.padding().glassEffect()
                Button("B") { }.padding().glassEffect()
            }
        }

        // This group uses the outer container's spacing
        HStack(spacing: 8) {
            Button("X") { }.padding().glassEffect()
            Button("Y") { }.padding().glassEffect()
        }
    }
}

The inner container creates a tightly-coupled group, while the outer container governs how those groups relate to each other.

Performance Considerations

GlassEffectContainer isn't just about visual consistency. Grouping glass elements improves rendering performance because the system can composite them together rather than processing each independently. For interfaces with several glass elements, always use a container.

That said, don't wrap your entire app in one giant container. Keep containers scoped to logically related groups of glass elements. A toolbar's buttons belong in one container; a separate floating action button can have its own.

Tips for Layout

When building layouts inside a container, keep the spacing values consistent. If your HStack uses 12-point spacing, set the container's spacing to at least 12 if you want blending, or higher if you want separation. Mismatched values can lead to unexpected results where some elements blend and others don't.

For buttons that should clearly appear as one unified control, use tight spacing. For elements that are related but distinct, increase the layout spacing beyond the container threshold.

Once you have your containers set up, you can add glassEffectID to individual elements to enable smooth morphing animations between different UI states.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.