如何实现 ios26 Photos app 右上角 liquid glass 按钮的交互动画?
优先使用 iOS 26 的系统工具栏和 Menu,让系统处理右上角按钮的玻璃外观、按压反馈和菜单呈现动画。需要自行控制按钮的展开、分离和融合时,再使用 GlassEffectContainer 与 glassEffectID(_:in:)。
题目未提供可查看的动图,无法确定目标效果是按压时的弹性反馈、菜单展开,还是一个按钮变成多个按钮。这几种效果的实现方式各有不同,也无法保证与 Photos 的内部实现完全一致。
优先实现系统工具栏菜单
使用支持 iOS 26 SDK 的 Xcode,在 NavigationStack 的 .toolbar 中放置 Menu,将位置设为 .topBarTrailing。系统工具栏会提供对应的 Liquid Glass 样式,不需要再给按钮叠加玻璃背景。Apple 也建议优先使用标准导航结构、工具栏和控件来采用新设计,参见 Build a SwiftUI app with the new design。
import SwiftUI
@available(iOS 26.0, *)
struct RecentlyDeletedView: View {
let onSelect: () -> Void
let onRecoverAll: () -> Void
var body: some View {
NavigationStack {
Text("最近删除的照片")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.navigationTitle("最近删除")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Menu {
Button(
"选择",
systemImage: "checkmark.circle",
action: onSelect
)
Button(
"恢复全部",
systemImage: "arrow.uturn.backward",
action: onRecoverAll
)
} label: {
Label("更多", systemImage: "ellipsis")
}
}
}
}
}
}
菜单项可替换为实际业务操作。如果页面已经在 NavigationStack 中,只需保留 .toolbar,不必再嵌套导航容器。
先运行这个最小示例,确认系统菜单是否符合预期。用 overlay 放置圆形按钮,再切换自定义面板,不会自动获得同样的系统菜单呈现行为。
如果只需要按钮的按压反馈
工具栏以外的独立按钮,优先使用以下写法:
Button("更多", systemImage: "ellipsis") {
// 处理点击
}
.buttonStyle(.glass)
.buttonBorderShape(.circle)
自行绘制的交互视图可以使用 .glassEffect(.regular.interactive(), in: .capsule)。其中,.interactive() 会启用玻璃对触摸的缩放、弹性和闪光反馈,点击后的业务操作仍需由 Button 等控件处理。它不负责菜单展开或多个按钮之间的融合动画。参见 Apple 的 Liquid Glass 交互效果说明。
如果需要按钮展开、分离和融合
要自定义展开动画,需要将相关玻璃元素放在同一个 GlassEffectContainer 中,为各元素设置稳定的 glassEffectID,再通过 withAnimation 改变状态。Apple 的 Landmarks 自定义徽章示例也采用了这种方式。
以下示例中,点击“更多”会展开一个额外操作,再次点击则收回:
import SwiftUI
@available(iOS 26.0, *)
struct ExpandingGlassActions: View {
@Namespace private var namespace
@State private var isExpanded = false
let onSelect: () -> Void
var body: some View {
GlassEffectContainer(spacing: 24) {
HStack(spacing: 12) {
if isExpanded {
Button(
"选择",
systemImage: "checkmark.circle",
action: onSelect
)
.buttonStyle(.glass)
.glassEffectID("select", in: namespace)
}
Button {
withAnimation {
isExpanded.toggle()
}
} label: {
Image(
systemName: isExpanded ? "xmark" : "ellipsis"
)
.frame(width: 24, height: 24)
}
.buttonStyle(.glass)
.buttonBorderShape(.circle)
.glassEffectID("toggle", in: namespace)
.accessibilityLabel(
isExpanded ? "收起操作" : "展开操作"
)
}
}
.padding()
}
}
这个自定义浮动控件可以放在页面右上角,但不会创建系统 Menu。
GlassEffectContainer(spacing:) 控制玻璃形状接近时开始融合的距离,HStack(spacing:) 控制实际布局间距。示例中的 24 和 12 仅用于演示,需要根据按钮尺寸调整。参见 GlassEffectContainer。
实现时需要检查的地方
- 同一容器内同时存在的玻璃元素,应使用不同且稳定的 ID,不要在每次刷新时生成新的
UUID()。 - 系统工具栏按钮已有玻璃背景,再叠加
.glassEffect()会造成重复材质。 - 自定义玻璃视图应先确定内容尺寸和内边距,再应用玻璃效果,否则玻璃边界可能不符合预期。
- 需要兼容旧系统时,用
if #available(iOS 26.0, *)选择上述视图,并为旧系统提供普通按钮或材质背景。 - 在目标设备上检查菜单呈现,以及深浅色模式和辅助功能设置下的效果。系统版本、背景内容和“减少动态效果”等设置都可能影响最终观感。
备注:内容仅供参考。
