SwiftUI iOS 26 如何按状态更新交互式玻璃的 tint 而不中断动画?
结论
原代码中的 .tint(saved ? overlayCol : overlayCol.opacity(0.1)) 写法有效。tint 随状态更新的动画和 .interactive() 提供的按压反馈是两种不同的动画行为。
可以先用 withAnimation 更新状态。如果仍出现“膨胀后突然缩回”,更换条件表达式也不一定能解决问题。查阅到的 Apple 文档没有说明如何在修改 tint 时保留正在进行的交互动画,也没有承诺 withAnimation 能消除这类中断。
如果可以调整交互效果,可以移除 .interactive(),让 GlassEffectContainer 处理状态切换时的玻璃过渡。这种做法改变了动画的实现方式,效果需要在目标系统和设备上验证。
为什么切换颜色可能影响按压反馈?
Apple 将 .interactive() 定义为让玻璃响应触摸和指针交互的配置;GlassEffectContainer 则用于协调多个玻璃视图的融合与过渡。两者的职责不同。Apple Liquid Glass 文档
从现象看,状态更新与按压结束时的反馈可能发生了重叠,可以从这个方向排查。仅凭这段代码,无法确认系统内部是否重建了效果,也不能断定问题由三元表达式引起。该表达式只是计算一个新的颜色值,没有显式切换两棵视图分支。
先尝试显式动画
保留现有 .glassEffect(...),只修改按钮操作:
Button(action: {
withAnimation {
saved.toggle()
}
}) {
// 保留原来的按钮内容
}
这样会让状态更新发生在动画事务中,但不保证玻璃 tint 的变化能与尚未结束的按压反馈连续衔接。如果长按后松开仍出现突变,就需要考虑替代交互,不宜继续叠加 .animation(...)。
替代方案:让容器处理状态过渡
如果希望点击后玻璃效果连贯变化,可以将两种状态放在同一个 GlassEffectContainer 中,移除按钮玻璃上的 .interactive(),并用 withAnimation 切换状态。
下面用 SF Symbols 展示代码结构;原代码中的 Image("...") 读取项目图片资源,使用时可以换回自己的资源名称。
import SwiftUI
@available(iOS 26.0, *)
struct BookmarkGlassButton: View {
@State private var saved = false
let overlayCol: Color
var body: some View {
GlassEffectContainer {
if saved {
bookmarkButton(isSaved: true)
.glassEffect(
.clear.tint(overlayCol),
in: .circle
)
} else {
bookmarkButton(isSaved: false)
.glassEffect(
.clear.tint(overlayCol.opacity(0.1)),
in: .circle
)
}
}
}
private func bookmarkButton(isSaved: Bool) -> some View {
Button {
withAnimation {
saved.toggle()
}
} label: {
Image(systemName: isSaved ? "bookmark.fill" : "bookmark")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.foregroundStyle(.white)
.padding(8)
}
.buttonStyle(.plain)
.accessibilityLabel(isSaved ? "取消收藏" : "收藏")
}
}
示例让条件分支中的玻璃效果参与容器过渡,并使用 .buttonStyle(.plain) 避免默认按钮样式带来额外表现。关于容器协调玻璃过渡、使用 withAnimation 驱动状态变化,可以参考 Apple 的自定义活动徽章示例。这段代码是针对当前问题组织的替代实现,未在目标设备上实测。
这个方案无法保留原来的按压膨胀反馈。如果必须完整保留 .interactive() 的长按、松手动画,它不能算作等价修复。
验证时需要注意什么?
分别检查快速点击、长按后松开和连续切换,确认异常出现在按压阶段还是状态过渡阶段。不要把 .glassEffectTransition(.identity) 当作关闭按压动画的开关:该 API 控制玻璃效果加入或移出视图层级时的过渡。Apple GlassEffectTransition 文档
另外,overlayCol.opacity(0.1) 仍然是带透明度的 tint,和不设置 tint 不同。仅凭 Phone 和 Reminders 的外观,无法断定它们使用了相同的 SwiftUI 实现。尤其是按钮内嵌的蓝色圆形,可能需要单独的视觉层,不能仅靠修改整块玻璃的 tint 来推断实现。
如果必须保留现有按压反馈,应记录实际 iOS 和 SDK 版本,用最小示例复现后提交 Feedback,并在目标版本上验证。现有材料不足以确认某个版本已经修复。
备注:内容仅供参考。
