SwiftUI Liquid Glass 完全指南:iOS 26 glassEffect 从入门到实战

从零开始掌握 iOS 26 Liquid Glass 的 SwiftUI 实现。涵盖 glassEffect 修饰符、GlassEffectContainer、变形动画、自定义组件封装,附完整代码示例和向后兼容方案。

说实话,第一次在 iOS 26 上看到 Liquid Glass 的效果时,我愣了好几秒。那种半透明、有折射、会跟着手指动的玻璃质感,真的和之前的毛玻璃完全不是一回事。如果你最近升级了 Xcode 26 或者看了 WWDC 2025 的回放,应该也有同样的感受——这是自 iOS 7 扁平化以来最大的一次视觉变革。

好消息是,Apple 给了我们一整套 SwiftUI API 来实现这个效果。坏消息?中文社区能找到的系统性教程少得可怜,基本都是零散的代码片段或者英文文档的机翻。

所以这篇文章来了。

我会从最基础的 .glassEffect() 修饰符讲起,一路覆盖到 GlassEffectContainer、变形动画、自定义组件封装,还有向后兼容和无障碍适配。每一步都附带可以直接跑的代码,看完之后你应该能在自己的项目里自信地用上 Liquid Glass。

Liquid Glass 到底是什么?

先澄清一点:Liquid Glass 不是一个简单的毛玻璃。它是一种动态材质,具备这些特性:

  • 实时折射和反射——玻璃下方的内容会被折射,表面会反射周围环境
  • 响应设备运动——倾斜 iPhone 时,玻璃上的高光会跟着动(这个细节太赞了)
  • 自适应阴影——根据背景内容自动调整阴影深度
  • 触摸交互反馈——按下时玻璃会有物理性的凹陷感

Apple 的设计意图很明确:Liquid Glass 是给导航层用的。工具栏、Tab Bar、浮动按钮——这些悬浮在内容之上的控件最适合。而列表、卡片、媒体这些主要内容区域就别用了,不然整个界面会变成一坨模糊。

记住这条原则,能帮你避开很多坑。

环境要求

动手写代码之前,先确认一下你的开发环境:

  • Xcode 26 或更高版本
  • 部署目标设为 iOS 26.0+
  • Swift 6 或更高版本

Liquid Glass 的 API 是 iOS 26 SDK 独有的。如果你的项目还得兼容旧版本,别急,后面专门有一节讲向后兼容。

glassEffect() 修饰符:一切的起点

.glassEffect() 是使用 Liquid Glass 最直接的方式。一行代码就够了:

import SwiftUI

struct BasicGlassDemo: View {
    var body: some View {
        Text("Hello, Liquid Glass!")
            .font(.headline)
            .padding()
            .glassEffect()
    }
}

就这么简单。默认情况下它会用 .regular 样式,形状是胶囊形。完整的方法签名长这样:

func glassEffect<S: Shape>(
    _ glass: Glass = .regular,
    in shape: S = DefaultGlassEffectShape,
    isEnabled: Bool = true
) -> some View

三个参数都是可选的,灵活组合就行。

Glass 样式类型

Glass 支持三种基础样式:

  • .regular — 标准的半透明玻璃效果,绝大多数场景用这个
  • .clear — 透明玻璃,保留光线折射但减少模糊
  • .identity — 无玻璃效果,主要用于无障碍降级

自定义形状

玻璃效果不一定非得是胶囊形,任何符合 Shape 协议的类型都能用:

struct GlassShapesDemo: View {
    var body: some View {
        VStack(spacing: 20) {
            // 胶囊形(默认)
            Text("胶囊")
                .padding()
                .glassEffect(.regular, in: .capsule)

            // 圆形
            Image(systemName: "star.fill")
                .font(.title)
                .frame(width: 60, height: 60)
                .glassEffect(.regular, in: .circle)

            // 圆角矩形
            Text("圆角矩形")
                .padding(.horizontal, 24)
                .padding(.vertical, 12)
                .glassEffect(.regular, in: .rect(cornerRadius: 12))

            // 自适应父容器圆角
            Text("容器适配")
                .padding()
                .glassEffect(
                    .regular,
                    in: .rect(cornerRadius: .containerConcentric)
                )
        }
    }
}

这里特别提一下 .containerConcentric——它会自动算出跟父容器同心的圆角半径,嵌套的玻璃元素看起来会特别和谐。个人觉得这是个被低估的小功能。

色调(Tint)

通过 .tint() 可以给玻璃染色:

struct TintedGlassDemo: View {
    var body: some View {
        HStack(spacing: 16) {
            Text("蓝色")
                .padding()
                .glassEffect(.regular.tint(.blue))

            Text("紫色")
                .padding()
                .glassEffect(.regular.tint(.purple.opacity(0.6)))

            Text("橙色")
                .padding()
                .glassEffect(.regular.tint(.orange))
        }
    }
}

色调不会把透明感盖掉,而是像给玻璃染了一层很薄的颜色。实际效果比你想象的要优雅。

交互效果(Interactive)

加上 .interactive(),玻璃在用户触摸时就会有物理反馈——按下去轻微凹陷,松开弹回来:

Button("点击我") {
    // 处理点击
}
.padding()
.glassEffect(.regular.interactive())

这个效果做自定义按钮的时候特别好用。当然 Apple 也给了现成的玻璃按钮样式,懒得自己写的话直接用就好:

Button("玻璃按钮") { }
    .buttonStyle(.glass)

Button("强调玻璃按钮") { }
    .buttonStyle(.glassProminent)

.glass 是标准玻璃样式,.glassProminent 会把按钮的 tint 颜色铺到整个玻璃表面,视觉上更抢眼。

GlassEffectContainer:多个玻璃的协调容器

页面上只有一个玻璃元素的时候一切都很美好。但当你有好几个玻璃元素挤在一起时,问题就来了。

核心原因:玻璃没法对另一个玻璃进行采样。两个玻璃元素靠太近,视觉上就会互相干扰,看着特别别扭。GlassEffectContainer 的解决方案很聪明——它会把距离够近的玻璃元素自动合并成一个统一的玻璃形状。

struct GlassContainerDemo: View {
    var body: some View {
        GlassEffectContainer {
            HStack(spacing: 12) {
                Button("首页") { }
                    .glassEffect()
                Button("搜索") { }
                    .glassEffect()
                Button("设置") { }
                    .glassEffect()
            }
            .padding()
        }
    }
}

三个按钮会自动融合成一个连续的玻璃形状。说真的,那个融合动画有点像水滴靠近时合并的感觉,很漂亮。

spacing 参数:控制融合阈值

GlassEffectContainerspacing 参数决定了多近才触发融合:

// 间距小于 40pt 的元素会融合
GlassEffectContainer(spacing: 40) {
    VStack(spacing: 30) {
        Button("按钮 A") { }.glassEffect()
        Button("按钮 B") { }.glassEffect()
        // 这两个按钮间距 30 < 40,会融合
    }
}

// 间距小于 10pt 才融合(更紧凑)
GlassEffectContainer(spacing: 10) {
    HStack(spacing: 20) {
        Button("按钮 A") { }.glassEffect()
        Button("按钮 B") { }.glassEffect()
        // 间距 20 > 10,不会融合,各自独立
    }
}

设计复杂工具栏的时候,调这个值可以精确控制哪些元素要分组在一起。

glassEffectID:流畅的变形动画

接下来讲 Liquid Glass 里我个人最喜欢的部分——变形动画(Morph Animation)。

想象一下:一个 + 按钮,点击后展开成一排操作选项,整个过程中玻璃形状像液体一样流畅地变形。听起来很复杂对吧?但实现起来真没几行代码:

struct MorphingFABDemo: View {
    @State private var isExpanded = false
    @Namespace private var glassNS

    var body: some View {
        GlassEffectContainer(spacing: 18) {
            VStack(spacing: 12) {
                // 主按钮
                Button {
                    withAnimation(.bouncy(duration: 0.35)) {
                        isExpanded.toggle()
                    }
                } label: {
                    Image(systemName: isExpanded ? "xmark" : "plus")
                        .font(.title2)
                        .frame(width: 56, height: 56)
                }
                .glassEffect(.regular.interactive())
                .glassEffectID("toggle", in: glassNS)

                // 展开的操作按钮
                if isExpanded {
                    Button {
                        // 拍照
                    } label: {
                        Image(systemName: "camera")
                            .frame(width: 48, height: 48)
                    }
                    .glassEffect()
                    .glassEffectID("camera", in: glassNS)

                    Button {
                        // 相册
                    } label: {
                        Image(systemName: "photo")
                            .frame(width: 48, height: 48)
                    }
                    .glassEffect()
                    .glassEffectID("photo", in: glassNS)

                    Button {
                        // 文件
                    } label: {
                        Image(systemName: "doc")
                            .frame(width: 48, height: 48)
                    }
                    .glassEffect()
                    .glassEffectID("doc", in: glassNS)
                }
            }
        }
    }
}

几个关键点说一下:

  • @Namespace 提供命名空间,让 SwiftUI 能追踪哪些元素是"同一个"
  • .glassEffectID("标识符", in: namespace) 给每个玻璃元素一个唯一身份
  • 搭配 withAnimation,元素出现和消失时会自动产生流畅的变形效果
  • .bouncy 动画曲线跟 Liquid Glass 的物理质感简直绝配

glassEffectTransition:控制出场方式

默认情况下,玻璃元素用 materialize(实体化)的方式出现和消失。你也可以通过 .glassEffectTransition 来自定义:

Button("操作") { }
    .glassEffect()
    .glassEffectID("action", in: glassNS)
    .glassEffectTransition(.materialize)

这个效果看起来就像元素从虚无中凝结成形,然后又化回虚无。非常"液态"。

glassEffectUnion:强制合并玻璃形状

有时候你需要多个元素共享同一块玻璃背景。最典型的例子就是 Apple 地图里的缩放控件——加号和减号按钮被一个统一的玻璃形状包裹着。

这时候就该 .glassEffectUnion 上场了:

struct ZoomControlDemo: View {
    @Namespace private var zoomNS

    var body: some View {
        GlassEffectContainer {
            VStack(spacing: 0) {
                Button {
                    // 放大
                } label: {
                    Image(systemName: "plus")
                        .frame(width: 44, height: 44)
                }
                .glassEffect(.regular)
                .glassEffectUnion(id: "zoom", namespace: zoomNS)

                Divider()
                    .frame(width: 30)

                Button {
                    // 缩小
                } label: {
                    Image(systemName: "minus")
                        .frame(width: 44, height: 44)
                }
                .glassEffect(.regular)
                .glassEffectUnion(id: "zoom", namespace: zoomNS)
            }
        }
    }
}

glassEffectID 的区别在于:glassEffectUnion 让元素始终显示为一个整体,不管距离多远。而前者只在距离够近时才融合。

实战:自定义 Liquid Glass 浮动 Tab Bar

好了,理论够多了,来做个真实的东西。

下面是一个悬浮在内容上方的自定义 Tab Bar,带 Liquid Glass 效果和选中状态动画。我自己的项目里就在用类似的实现:

struct LiquidGlassTabBar: View {
    @Binding var selectedTab: Int
    @Namespace private var tabNS

    let tabs: [(icon: String, label: String)] = [
        ("house", "首页"),
        ("magnifyingglass", "搜索"),
        ("heart", "收藏"),
        ("person", "我的")
    ]

    var body: some View {
        GlassEffectContainer(spacing: 8) {
            HStack(spacing: 4) {
                ForEach(Array(tabs.enumerated()), id: \.offset) { index, tab in
                    Button {
                        withAnimation(.smooth(duration: 0.3)) {
                            selectedTab = index
                        }
                    } label: {
                        VStack(spacing: 4) {
                            Image(systemName: selectedTab == index
                                ? "\(tab.icon).fill" : tab.icon)
                                .font(.system(size: 20))
                            Text(tab.label)
                                .font(.caption2)
                        }
                        .frame(maxWidth: .infinity)
                        .padding(.vertical, 8)
                    }
                    .foregroundStyle(
                        selectedTab == index ? .primary : .secondary
                    )
                    .glassEffect(
                        selectedTab == index
                            ? .regular.tint(.blue.opacity(0.3))
                            : .clear,
                        in: .capsule
                    )
                    .glassEffectID("tab-\(index)", in: tabNS)
                }
            }
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
            .glassEffect(.regular, in: .capsule)
        }
    }
}

// 使用方式
struct ContentView: View {
    @State private var selectedTab = 0

    var body: some View {
        ZStack(alignment: .bottom) {
            // 你的主要内容
            TabContent(selectedTab: selectedTab)

            LiquidGlassTabBar(selectedTab: $selectedTab)
                .padding(.horizontal, 40)
                .padding(.bottom, 20)
        }
    }
}

这个实现的巧妙之处在于:外层一个大胶囊形玻璃做整体背景,选中的 Tab 又有自己带色调的小玻璃高亮。切换 Tab 时,高亮通过 glassEffectID 流畅地滑动变形,体验非常丝滑。

实战:Liquid Glass 卡片组件

虽然 Apple 建议不要在内容层用 Liquid Glass,但有一种情况例外——悬浮在内容之上的信息卡片(播放器控件、快捷操作面板之类的),用玻璃效果反而很合适:

struct GlassInfoCard: View {
    let title: String
    let subtitle: String
    let icon: String

    var body: some View {
        HStack(spacing: 16) {
            Image(systemName: icon)
                .font(.system(size: 28))
                .foregroundStyle(.blue)
                .frame(width: 48, height: 48)
                .glassEffect(.regular.tint(.blue.opacity(0.2)), in: .circle)

            VStack(alignment: .leading, spacing: 4) {
                Text(title)
                    .font(.headline)
                Text(subtitle)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }

            Spacer()

            Image(systemName: "chevron.right")
                .foregroundStyle(.tertiary)
        }
        .padding()
        .glassEffect(.regular, in: .rect(cornerRadius: 16))
    }
}

// 使用示例
struct CardListDemo: View {
    var body: some View {
        ZStack {
            // 背景图片
            Image("background")
                .resizable()
                .ignoresSafeArea()

            VStack(spacing: 12) {
                GlassInfoCard(
                    title: "正在播放",
                    subtitle: "周杰伦 - 晴天",
                    icon: "music.note"
                )
                GlassInfoCard(
                    title: "下一个日程",
                    subtitle: "14:00 团队会议",
                    icon: "calendar"
                )
            }
            .padding()
        }
    }
}

向后兼容:支持 iOS 26 以下版本

现实情况是,大部分应用不可能只支持最新系统。如果你的应用还得兼容 iOS 26 之前的版本,glassEffect 相关 API 直接就编译不过。

解决方案是用 #available 做版本隔离。不过与其到处写判断,不如封装一个通用的 View Extension,一劳永逸:

extension View {
    @ViewBuilder
    func adaptiveGlass(
        in shape: some Shape = .capsule
    ) -> some View {
        if #available(iOS 26.0, *) {
            self.glassEffect(.regular, in: shape)
        } else {
            self.background(.ultraThinMaterial, in: shape)
        }
    }

    @ViewBuilder
    func adaptiveGlassInteractive(
        in shape: some Shape = .capsule
    ) -> some View {
        if #available(iOS 26.0, *) {
            self.glassEffect(.regular.interactive(), in: shape)
        } else {
            self
                .background(.ultraThinMaterial, in: shape)
                .shadow(color: .black.opacity(0.1), radius: 4)
        }
    }
}

这样业务代码里只要调 .adaptiveGlass() 就行了。iOS 26 自动走 Liquid Glass,旧版本优雅降级为 .ultraThinMaterial。干净利落。

无障碍(Accessibility)适配

Liquid Glass 效果好看归好看,但不是所有用户都适合。有些用户对透明效果敏感,iOS 提供了"减少透明度"选项,你的应用得尊重这个设置:

struct AccessibleGlassView: View {
    @Environment(\.accessibilityReduceTransparency)
    var reduceTransparency

    var body: some View {
        Text("自适应玻璃")
            .padding()
            .glassEffect(
                reduceTransparency ? .identity : .regular,
                in: .capsule
            )
    }
}

.identity 会完全移除玻璃效果,让视图回到原始外观。大多数时候系统会自动处理这件事,但如果你做了高度自定义的玻璃组件,手动检查一下这个环境变量还是有必要的。

性能优化建议

Liquid Glass 涉及实时渲染——模糊、折射、反射,这些都是有性能开销的。不注意的话帧率说掉就掉。

  • 避免大面积使用——玻璃面积越大渲染越重,用在导航控件上就够了,别铺满屏幕
  • 控制嵌套层数——玻璃对玻璃采样效果很差,多层嵌套既丑又费性能
  • 善用 GlassEffectContainer——它不光让视觉更协调,合并渲染还能提升性能,一举两得
  • 别在滚动列表里加玻璃——快速滚动 + 玻璃渲染 = 掉帧,亲测
  • 用 Instruments 验证——Core Animation 工具能帮你定位玻璃渲染导致的性能瓶颈

常见设计反模式

用了几周 Liquid Glass 之后,我总结了一些常见的坑:

  • 不要给列表的每一行加玻璃——视觉过载,整个列表变成一片模糊,用户根本看不清内容
  • 不要叠加多层玻璃——会产生奇怪的视觉伪影,特别难看
  • 不要在纯色背景上用——Liquid Glass 的魅力在于折射下面丰富的内容,纯色背景上它就是个灰色块,完全没有质感
  • 注意 Tab Bar 的可读性——内容在下面滚动时,彩色内容可能会让玻璃上的文字变得模糊不清

Tab Bar 可读性修复

上面提到的 Tab Bar 可读性问题其实很好解决。加一个底部渐变遮罩就行:

struct TabBarFadeModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .overlay(alignment: .bottom) {
                LinearGradient(
                    colors: [
                        .clear,
                        Color(.systemBackground).opacity(0.8)
                    ],
                    startPoint: .top,
                    endPoint: .bottom
                )
                .frame(height: 100)
                .allowsHitTesting(false)
            }
    }
}

extension View {
    func tabBarFade() -> some View {
        modifier(TabBarFadeModifier())
    }
}

// 在你的可滚动内容上使用
ScrollView {
    // 你的内容
}
.tabBarFade()

简单粗暴但有效。

与 UIKit 混合使用

如果你的项目是 UIKit 和 SwiftUI 混合架构(说实话,大部分生产项目都是),UIKit 也有对应的 Liquid Glass API:

// UIKit 中创建玻璃效果
let container = UIGlassContainerEffect()
let containerView = UIVisualEffectView(effect: container)

let glassEffect = UIGlassEffect()
let glassView = UIVisualEffectView(effect: glassEffect)
containerView.contentView.addSubview(glassView)

// 启用交互反馈
glassEffect.isInteractive = true

// 移除时使用 effect = nil(不要用 alpha)
UIView.animate(withDuration: 0.3) {
    glassView.effect = nil
}

这里有个容易踩的坑:移除玻璃效果时,一定要通过 effect = nil 来触发 dematerialization 动画,千万别改 alpha。改 alpha 不会有那个优雅的消失效果,而且可能导致视觉 bug。

常见问题解答

Liquid Glass 会在现有应用上自动生效吗?

部分会。用 Xcode 26 重新编译并以 iOS 26 为目标之后,系统内置组件(NavigationBar、TabBar、Toolbar 等)会自动获得 Liquid Glass 外观,不用改一行代码。但你自定义的控件不会自动变,得手动加 .glassEffect()

glassEffect 和 .ultraThinMaterial 有什么区别?

.ultraThinMaterial 是 iOS 15 引入的静态毛玻璃——只有模糊和半透明,仅此而已。.glassEffect() 则是全新的动态材质,折射、反射、设备运动响应、触摸交互反馈全都有。两者的视觉层次感完全不在一个级别。当然了,对于还得兼容旧版本的项目,.ultraThinMaterial 仍然是最好的降级方案。

watchOS 和 tvOS 上能用吗?

可以。Apple 在 WWDC 2025 宣布 Liquid Glass 会覆盖所有平台,包括 watchOS 26 和 tvOS 26。不过具体 API 的可用性各平台可能有差异,建议查一下对应平台的 SDK 文档。

用 Liquid Glass 会影响 App Store 审核吗?

不会。这是 Apple 自家的设计语言和 SDK 组件,正常用完全没问题。事实上,积极适配新设计语言的应用更容易获得 App Store 的编辑推荐。

怎么在 SwiftUI Preview 中调试?

Liquid Glass 在 Preview 中可以正常渲染,但由于 Preview 没有设备运动数据,倾斜高光之类的动态效果看不到。最终的视觉验证还是得上真机(或者至少用模拟器)。另外有个小技巧:Preview 的背景别用纯色,换成一张图片,折射效果会明显很多。

关于作者 Editorial Team

Our team of expert writers and editors.