如何在SwiftUI的交互式玻璃上有条件地设置tint颜色?
我在SwiftUI(iOS 26)中使用新的 .glassEffect(.interactive()) API,想基于状态来改变tint颜色。
然而,当我用三元表达式有条件地改变tint时,交互式玻璃效果的动画会在中途被打断——玻璃会膨胀起来,然后立即缩小。
下面是示例:
.overlay(alignment: .topTrailing) {
Button(action: { saved.toggle() }) {
Image(saved ? "bookmark" : "bookmark.outline")
.resizable()
.scaledToFit()
.frame(width: 14, height: 14)
.foregroundStyle(.white)
.padding(8)
}
.glassEffect(
.clear
.tint(saved ? overlayCol : overlayCol.opacity(0.1))
.interactive()
)
.padding(.top, 8)
.padding(.trailing, 8)
}
在不打断或重启动画的前提下,正确更新交互式玻璃效果tint颜色的方法是什么?在原生应用里,比如电话应用,当你筛选来电时,按钮会从无tint变为蓝色tint(不过有点不同,因为蓝色tint看起来像按钮内部嵌入的一个圆圈)。我还能想到的下一个更接近的例子是提醒事项应用中打开列表时,底部工具栏中的+ 按钮从蓝色变成橙色。
解决方案
如果你使用 GlassEffectContainer 来承装按钮的两个变体,然后在它们之间切换,可以获得相当流畅、带弹性的动画。对标志的修改要在 withAnimation 的时机进行。
下面的示例展示了你得到的效果。这个示例使用 .glass 作为按钮样式,但如果你自己应用玻璃效果,也像之前那样有效。
struct ContentView: View {
let overlayCol = Color.pink
@State private var saved = false
var body: some View {
somethingToBookmark
.overlay(alignment: .topTrailing) {
GlassEffectContainer {
if saved {
Button("Unsave", systemImage: "bookmark.fill") {
withAnimation { saved = false }
}
.buttonStyle(.glass(.clear.tint(overlayCol)))
} else {
Button("Save", systemImage: "bookmark") {
withAnimation { saved = true }
}
.buttonStyle(.glass(.clear.tint(overlayCol.opacity(0.4))))
}
}
.foregroundStyle(.white)
.labelStyle(.iconOnly)
.buttonBorderShape(.circle)
.offset(x: 10, y: -10)
}
}
private var somethingToBookmark: some View {
Text("Something to bookmark")
.font(.title3)
.fontWeight(.bold)
.padding(10)
.background(.white.opacity(0.5), in: .rect(cornerRadius: 10))
.padding(20)
.background(.image(Image(systemName: "xmark")).opacity(0.3))
.background(.gray.opacity(0.1), in: .rect(cornerRadius: 16))
}
}

编辑:你在评论中提到:
按下按钮的动画与玻璃容器中发生的切换动画冲突
确实有两个动画在同时发生,并且并未合并:
- 按钮的大小会根据按住的时长而增大或缩小。这是随玻璃按钮自带的交互式玻璃效果。
- 玻璃容器在一个按钮被另一个按钮替换时提供一个带弹性的动画。
按钮动画仍然存在你在问题中提到的问题,即按住并释放时它会在没有动画的情况下变成更小的尺寸。
你在这里使用按钮来切换一个布尔标志的状态。就此而言,随玻璃容器带来的动画也许就足够(且在我看来相当令人满意),按钮动画则不是必需的。所以你可能考虑完全禁用交互式按钮效果。
不幸的是,将修饰符 .interactive(false) 应用到作为参数提供给 glass 按钮样式的玻璃上不起作用:
.buttonStyle(.glass(
.clear
.tint(overlayCol)
.interactive(false) // 🙁 Doesn't work
))
不过,你可以通过将 .glassEffect 应用于按钮标签自己来禁用交互式玻璃效果(就像你最初做的那样),并且不对玻璃应用 .interactive()。如下所示更新示例,使其以这种方式工作:
GlassEffectContainer {
if saved {
Button {
withAnimation { saved = false }
} label: {
Image(systemName: "bookmark.fill")
.resizable()
.scaledToFit()
.frame(width: 18, height: 18)
.padding(8)
}
.glassEffect(.clear.tint(overlayCol), in: .circle)
} else {
Button {
withAnimation { saved = true }
} label: {
Image(systemName: "bookmark")
.resizable()
.scaledToFit()
.frame(width: 18, height: 18)
.padding(8)
}
.glassEffect(.clear.tint(overlayCol.opacity(0.4)), in: .circle)
}
}
至于如何解答这个问题:
如何在SwiftUI iOS 26中有条件地为交互式玻璃设置tint颜色?
这种方法仍然提供交互式玻璃效果,但效果来自于 GlassEffectContainer,而不是来自于 Button。