Liquid Glass and modern SwiftUI
What actually changed in SwiftUI for iOS 26 and iOS 27, what to adopt now, and what to leave alone.
By the end you will be able to
- Apply Liquid Glass correctly, and say which layer of an interface it belongs to
- Use the current toolbar, tab and scroll APIs
- Decide what to adopt against a given deployment target
Liquid Glass is the material introduced across Apple's platforms in iOS 26. Where should you apply it?
The current landscape
Two releases matter right now, and knowing which is which decides your deployment target.
| iOS 26 (shipped Sept 2025) | iOS 27 (WWDC 2026, shipping autumn 2026) | |
|---|---|---|
| Design | Liquid Glass across the system | Automatic adoption; refinements |
| Toolbars | .toolbar placements | visibilityPriority, overflow menus, pinned trailing, minimize-on-scroll |
| Lists | List, ForEach | Drag-to-reorder in any container; swipe actions in any scroll view |
| Images | AsyncImage | HTTP caching by default |
| State | @State as a property wrapper | @State as a macro (Xcode 27 — back-deploys to older OSes), lazy initialisation of stored classes |
| Builders | per-container ViewBuilder overloads | unified ContentBuilder, materially faster type-checking |
Everything below is marked with the version it needs.
Liquid Glass (iOS 26+)
Text("Now playing")
.padding()
.glassEffect()
The default is .regular. .clear is more transparent, for use over imagery.
.glassEffect(.regular, in: .capsule)
.glassEffect(.clear.interactive(), in: .rect(cornerRadius: 20))
.interactive() adds the scale and shimmer response on touch. Apply the effect after layout and styling modifiers — it works on the final shape of the view. Order it wrong and you can see why: .glassEffect().padding() pours the material over the bare label and then pads outside it — a tight glass pill floating in empty inset — while .padding().glassEffect() pours it over the padded control, the full-size bar you meant. Same modifiers, two different pieces of glass.
GlassEffectContainer
When several glass elements sit near each other, wrap them so they sample the same backdrop and blend as one material rather than stacking:
GlassEffectContainer(spacing: 12) {
HStack(spacing: 12) {
Button { } label: { Image(systemName: "backward.fill") }
.glassEffect()
Button { } label: { Image(systemName: "play.fill") }
.glassEffect()
Button { } label: { Image(systemName: "forward.fill") }
.glassEffect()
}
}
Without the container, three separate glass layers overlap and the result looks muddy. With it, they merge — and morph into one another when they move close.
Toolbars (iOS 27)
The 2026 additions exist because iPhone apps became resizable, so a toolbar has to survive widths it was never designed for.
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button("Share") { }
.visibilityPriority(.high) // keep this one visible longest
Button("Duplicate") { }
Button("Info") { }
}
ToolbarOverflowMenu { // always in the "…" menu
Button("Export") { }
Button("Print") { }
}
}
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)
visibilityPriority(_:)— which items survive as space shrinks.ToolbarOverflowMenu— items that live in the overflow menu permanently..topBarPinnedTrailing— a placement that is never collapsed..toolbarMinimizeBehavior(.onScrollDown,for:)— collapse the bar while scrolling down.
Tabs
TabView {
Tab("Home", systemImage: "house") { HomeView() }
Tab("Search", systemImage: "magnifyingglass", role: .search) { SearchView() }
Tab("Create", systemImage: "plus", role: .prominent) { CreateView() } // iOS 27
}
.tabViewStyle(.sidebarAdaptable)
The Tab builder replaced .tabItem in iOS 18. role: .search gets the system search treatment; role: .prominent (iOS 27) visually elevates a primary action.
Reorderable containers (iOS 27)
Drag-to-reorder used to require List and onMove. Now any container can do it:
LazyVGrid(columns: columns) {
ForEach(items) { item in
ItemCell(item: item)
}
.reorderable()
}
.reorderContainer(for: Item.self) { difference in
difference.apply(to: &items)
}
AsyncImage caching (iOS 27)
AsyncImage now respects standard HTTP cache headers with no code change — a real fix for the long-standing complaint that it re-downloaded on every appearance. For finer control:
.asyncImageURLSession(URLSession(configuration: myConfiguration))
Scroll effects (iOS 17–18 — older than they look)
ScrollView {
ForEach(items) { item in
Row(item: item)
.scrollTransition { content, phase in
content
.opacity(phase.isIdentity ? 1 : 0.4)
.scaleEffect(phase.isIdentity ? 1 : 0.9)
}
}
}
.scrollTargetBehavior(.viewAligned)
.onScrollGeometryChange(for: CGFloat.self) { geometry in
geometry.contentOffset.y
} action: { _, offset in
isScrolled = offset > 0
}
scrollTransition gives per-item entry and exit effects; onScrollGeometryChange replaces the GeometryReader-in-a-ScrollView hacks people used to write. Availability worth knowing for target decisions: scrollTransition and .scrollTargetBehavior(.viewAligned) are iOS 17, onScrollGeometryChange is iOS 18 — none of this needs a modern target, it is just underused.
@State becomes a macro (Xcode 27)
In Xcode 27, @State is a macro rather than a dynamic property. This is a compiler change, not an OS one — it back-deploys to the iOS 17 era, so it does not raise your deployment target. The practical effect: a class stored in @State is initialised lazily, once per view lifetime, instead of on every body evaluation.
@State private var model = DataModel() // now constructed once, on first use
There is one migration trap. Giving a property a default value and assigning it again in init behaves differently under the macro — keep one or the other: either the inline default, or the assignment in init, not both.
Your deployment target is iOS 26. Which of these can you use today?
Glass control bar
Build a horizontal control bar over a gradient: three circles in an HStack, padded, with a capsule-shaped regular glass effect.
Pick an app on your phone that has adopted Liquid Glass. Write down which elements use it and which do not, and whether that matches the functional-layer rule. Note one place where you think they got it wrong, and why.
You can now:
- Apply
.glassEffectandGlassEffectContainerin the right layer - Use the iOS 27 toolbar, tab and reordering APIs
- Say which APIs need iOS 26 and which need iOS 27
- Name the
@Statemacro migration trap
Next up: keep building. The curriculum's remaining tracks cover lists and navigation, animation, SwiftData, architecture, testing, and shipping.