Highlight
In 2025, SwiftUI gets the Liquid Glass design system, a 6× speedup for macOS list loading, the
@Animatablemacro, WebView, Chart3D, rich-text bindings for TextEditor, and the ability to bridge SwiftUI scenes into UIKit/AppKit apps.
Core content
Every developer who has shipped a list has hit these walls: a List of 100,000 rows on macOS stutters on refresh and drops frames while scrolling; animating a custom Shape forces you to write a long AnimatableData that unpacks every property by hand; you want to use a SwiftUI-only scene like MenuBarExtra inside an existing UIKit app and find no way in. SwiftUI 2025 answers all of these head on.
Anna and Peter use a hike-planning app to thread the updates together. After a recompile, the app picks up the Liquid Glass look automatically: the sidebar gains a glass material, toolbar items morph during navigation transitions, and on iPhone the search bar moves to the bottom where it sits better under the thumb. Underneath, list loading is 6× faster and updates 16× faster, the scroll scheduler is rewritten to cut frame drops at high refresh rates, and the new SwiftUI Performance Instrument adds dedicated lanes that pinpoint slow body updates and slow platform view updates. The @Animatable macro lets SwiftUI synthesize animatable properties for you. Scene Bridging lets a UIKit/AppKit app consume SwiftUI-only scenes such as MenuBarExtra and ImmersiveSpace directly. WebView, Chart3D, and the AttributedString binding on TextEditor fill in view capabilities that have been missing for years.
Detailed content
Liquid Glass toolbar grouping (02:27). The new ToolbarSpacer API gives you explicit control over how toolbar items are grouped:
NavigationStack {
TripList()
.toolbar {
ToolbarItemGroup(placement: .primaryAction) {
UpButton()
DownButton()
}
ToolbarSpacer(.fixed, placement: .primaryAction)
ToolbarItem(placement: .primaryAction) {
SettingsButton()
}
}
}
Key points:
ToolbarItemGroupcollapses Up and Down into a single capsule.ToolbarSpacer(.fixed, placement: .primaryAction)inserts a fixed gap so SettingsButton becomes its own Liquid Glass capsule.- During navigation transitions, capsules morph along this group structure.
Search as its own Tab (04:12). Declare search as a Tab with role: .search and the system separates it visually from regular tabs and morphs it directly into a search field on tap:
TabView {
Tab("Summary", systemImage: "heart") {
NavigationStack { Text("Summary") }
}
Tab("Sharing", systemImage: "person.2") {
NavigationStack { Text("Sharing") }
}
Tab(role: .search) {
NavigationStack { Text("Search") }
}
}
.searchable(text: $text)
Key points:
Tab(role: .search)marks this as the search destination..searchablestill hangs off theTabViewand bindstext.- On iPhone, tapping the tab morphs the search field out from the tab’s position rather than pushing a new layer.
The @Animatable macro (11:24). Animating a few properties on a custom Shape used to mean hand-writing AnimatableData that nested values into AnimatablePair. Now hand it to the macro:
@Animatable
struct LoadingArc: Shape {
var center: CGPoint
var radius: CGFloat
var startAngle: Angle
var endAngle: Angle
@AnimatableIgnored var drawPathClockwise: Bool
func path(in rect: CGRect) -> Path {
return Path()
}
}
Key points:
@AnimatablesynthesizesanimatableDataand threads every animatable property through it.@AnimatableIgnoredmarks boolean properties likedrawPathClockwiseas not interpolated.- No more nested
AnimatablePair<AnimatablePair<...>, ...>boilerplate.
WebView + WebPage (20:44). WebKit finally has a native SwiftUI view. The simplest form takes a URL, but for programmatic navigation use WebPage:
struct InAppBrowser: View {
@State private var page = WebPage()
var body: some View {
WebView(page)
.ignoresSafeArea()
.onAppear {
page.load(URLRequest(url: sunshineMountainURL))
}
}
}
Key points:
WebPageis an Observable model designed for Swift, not a thin wrapper overWKWebView.WebView(page)takes aWebPageinstance and renders the page it drives.page.load(_:)triggers navigation; the same API also reads page properties, calls JavaScript, and serves custom URL schemes.
3D charts (21:35). Swift Charts adds Chart3D, which can draw surfaces:
Chart3D {
SurfacePlot(x: "x", y: "y", z: "z") { x, y in
sin(x) * cos(y)
}
.foregroundStyle(Gradient(colors: [.orange, .pink]))
}
.chartXScale(domain: -3 ... 3)
.chartYScale(domain: -3 ... 3)
.chartZScale(domain: -3 ... 3)
Key points:
Chart3Dis a new container; pair it withSurfacePlotto render a surface.- The closure
(x, y) -> zhands SwiftUI a math function directly; no pre-sampling needed. - The three
chart*Scalemodifiers bound the visible domain.
Drag and Drop with multi-select and delete (22:18). Drag and drop on the Mac has long been the worst offender for UIKit/AppKit bridging. This year fills the gap:
ScrollView {
LazyVGrid(columns: gridColumns) {
ForEach(model.photos) { photo in
view(photo: photo)
.draggable(containerItemID: photo.id)
}
}
}
.dragContainer(for: Photo.self, selection: selectedPhotos) { draggedIDs in
photos(ids: draggedIDs)
}
.dragConfiguration(DragConfiguration(allowMove: false, allowDelete: true))
.onDragSessionUpdated { session in
let ids = session.draggedItemIDs(for: Photo.ID.self)
if session.phase == .ended(.delete) {
trash(ids)
deletePhotos(ids)
}
}
.dragPreviewsFormation(.stack)
Key points:
draggable(containerItemID:)marks each cell as a draggable item inside the container, passing only the ID.dragContainer(for:selection:)runs only when a drop actually happens, lazily loading the realPhotodata.DragConfiguration(allowDelete: true)lets a drag onto the trash trigger a delete.onDragSessionUpdatedwatches forsession.phase == .ended(.delete)and does the real cleanup..dragPreviewsFormation(.stack)stacks the dragged images into a single pile as the drag preview.
Rich text in TextEditor (23:55). When TextEditor takes an AttributedString binding, the toolbar comes with bold, italic, list, and other format controls built in:
struct CommentEditor: View {
@Binding var commentText: AttributedString
var body: some View {
TextEditor(text: $commentText)
}
}
Key points:
- Switch the binding from
StringtoAttributedStringand rich text works out of the box. - The system supplies formatting controls; developers can also customize paragraph styles or restrict the allowed attribute set.
Core takeaways
-
What to do: remove
UIRequiresFullscreenfrom Info.plist and adapt your app to free window resizing.- Why it’s worth doing: this key is deprecated in iPadOS 26 and leaving it in restricts how your app behaves in iPad multitasking windows.
- How to start: delete the key, run the app, and check how
NavigationSplitViewcolumns collapse at narrow widths and whether custom layouts break at extreme sizes.
-
What to do: use the SwiftUI Performance Instrument as the first pass for performance work, instead of Time Profiler.
- Why it’s worth doing: the generic Time Profiler shows you a CPU flame graph; the new Instrument tells you directly which view’s
bodyran too many times and which platform view updates were slowest. - How to start: open Instruments in Xcode 26, pick the SwiftUI template, run your most complex list or animation scenario, and read the long body update lane first.
- Why it’s worth doing: the generic Time Profiler shows you a CPU flame graph; the new Instrument tells you directly which view’s
-
What to do: replace
animatableDataon existing custom Shapes with@Animatable.- Why it’s worth doing: nested
AnimatablePairis universally recognized boilerplate; the macro removes it and readability jumps a level. - How to start: add
@Animatableto the Shape, mark non-animating properties with@AnimatableIgnored, delete the hand-writtenanimatableData, and re-run your existing animation tests.
- Why it’s worth doing: nested
-
What to do: evaluate whether Scene Bridging can let your existing UIKit/AppKit app open
MenuBarExtra/ImmersiveSpace/RemoteImmersiveSpace.- Why it’s worth doing: for older projects that don’t plan to migrate to the SwiftUI lifecycle wholesale, this is the cheapest path to wire Vision Pro stereoscopic content and Mac menu bar features into the app.
- How to start: declare a SwiftUI scene from the UIKit/AppKit entry point, start with a minimal
MenuBarExtrafor verification, then considerRemoteImmersiveSpacetogether with CompositorServices.
-
What to do: replace your old wrappers around embedded H5 pages with
WebView+WebPage.- Why it’s worth doing: old
WKWebViewbridges typically use a JavaScript bridge for interaction, which is type-unsafe and hard to debug;WebPageis an Observable model, so navigation and page properties are reachable from pure Swift. - How to start: move display-only pages (FAQ, terms) to
WebView(url:)first; migrate interactive pages toWebView(page)+page.load(_:)and useWebPageto listen for navigation events.
- Why it’s worth doing: old
Related sessions
- Build a SwiftUI app with the new design — best practices for building a SwiftUI app from scratch with the new design system
- Meet WebKit for SwiftUI — the full API and advanced uses of
WebViewandWebPage - Optimize SwiftUI performance with Instruments — how to read each lane in the new SwiftUI Instrument
- Better together: SwiftUI and RealityKit — Observable Entity, coordinate conversion, and PresentationComponent
- Meet SwiftUI spatial layout — spatial layout tools like
Alignment3DandspatialOverlay
Comments
GitHub Issues · utterances