Before iOS 26, faking Apple's frosted-glass chrome meant stacking .ultraThinMaterial behind a shape, then hand-tuning opacity and a stroke overlay until it roughly matched the system controls sitting next to it. It never refracted. It never reacted to the content behind it the way native glass does.
#The old way
Text("42")
.font(.headline)
.padding()
.background(.ultraThinMaterial, in: Circle())
.overlay(Circle().strokeBorder(.white.opacity(0.2)))
#The new way
struct AdaptiveBadge: View {
let count: Int
var body: some View {
let label = Text("\(count)")
.font(.headline)
.foregroundStyle(.primary)
.padding()
if #available(iOS 26, *) {
label.glassEffect(.regular.tint(.blue).interactive(), in: .circle)
} else {
label.background(.ultraThinMaterial, in: Circle())
}
}
}
#Why it matters
- Refraction and specular highlights sampled from the content actually sitting behind the view. Not a flat blur.
.tint(_:)and.interactive()chain straight onto.regularor.prominent, with no extra modifiers- Tracks system chrome for free as Apple keeps tuning the material across OS updates
#Gotcha
Order matters. .glassEffect() goes after your layout and appearance modifiers, never before. That means after padding, font, and background color. Put it first and you glass an unstyled, unpadded view, with everything that follows composited on top of the glass instead of inside it.
This is also new API. Check GlassEffectStyle's exact case list and defaults against current docs before you rely on anything beyond .regular, .prominent, .tint(_:), and .interactive().