WWDC Quick Look 💓 By SwiftGGTeam
What's new in SwiftUI

What's new in SwiftUI

Watch original video

Highlight

This SwiftUI update covers a lot of ground, but if you remember one thing, remember this: for the past five years, any SwiftUI app that needed rich text editing had to bridge UITextView and write piles of UIViewRepresentable boilerplate. Now TextEditor natively accepts AttributedString, selection management, and find navigation. That bridge layer can finally come down.


Core content

How painful was it to write a notes-style app with bold, italic, and colored highlights in SwiftUI? You had to give up SwiftUI’s TextEditor, wrap a UITextView in UIViewRepresentable, handle selection sync, the keyboard toolbar, and the undo stack yourself, and then wire NSAttributedString to SwiftUI state. A simple-looking feature often took two or three hundred lines of glue code. WWDC25’s SwiftUI cuts that path straight: TextEditor now takes an AttributedString binding directly, and selection management is a first-class citizen.

Liquid Glass is the other main thread of the same release. It reaches across the whole visual system, far beyond any single API. The system’s TabBar, Toolbar, and buttons get the new look automatically under the new SDK. But any hand-rolled glass blur, custom background color, or ZStack overlay you wrote in the past may clash with the new material. Apple ships a glassEffect() modifier so custom views can join this material language too, plus a new ToolbarSpacer for grouping toolbar buttons. Add the @Animatable macro that synthesizes the Animatable protocol for you, and manipulable() on visionOS that lets any 3D view be grabbed and scaled with hands — this update lands across iOS, iPadOS, macOS, and visionOS at the same time.


Details

Toolbar grouping and Liquid Glass buttons. The new ToolbarSpacer splits toolbar buttons into separate groups. With Liquid Glass, each group becomes its own capsule.

import SwiftUI

struct TripDetailView: View {
    var body: some View {
        NavigationStack {
            TripList()
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        UpButton()
                        DownButton()
                    }

                    ToolbarSpacer(.fixed, placement: .primaryAction)

                    ToolbarItem(placement: .primaryAction) {
                        SettingsButton()
                    }
                }
        }
    }
}

Key points:

  • ToolbarItemGroup puts the up and down arrow buttons inside one capsule, visually a single group.
  • ToolbarSpacer(.fixed, placement: .primaryAction) inserts a fixed gap after them; Liquid Glass uses that gap to render the next button as a separate capsule.
  • SettingsButton is therefore split off from the previous two, so not every action is crammed into the same pill.

Adding glass material to a custom view. System controls adopt Liquid Glass automatically, but a floating button or hint card you draw yourself has to opt in.

import SwiftUI

struct ToTopButton: View {
    var body: some View {
        Button("To Top", systemImage: "chevron.up") {
            scrollToTop()
        }
        .padding()
        .glassEffect()
    }

    func scrollToTop() {
        // Scroll to top of view
    }
}

Key points:

  • .glassEffect() is a new view modifier that renders the view as Liquid Glass material.
  • It replaces the old hand-rolled combo of .background(.ultraThinMaterial) + .clipShape(Capsule()).
  • padding() comes first so the glass region includes the button’s inner padding instead of hugging the text.

The @Animatable macro kills the boilerplate. Animating a custom Shape used to mean writing animatableData by hand and packing several properties into nested AnimatablePairs. One macro now does the job.

import SwiftUI

@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 {
        // Creates a `Path` arc using properties
        return Path()
    }
}

Key points:

  • @Animatable goes on a Shape and synthesizes animatableData at compile time, threading every interpolatable property together.
  • @AnimatableIgnored marks properties that don’t animate — discrete values like Bool can’t be interpolated and must be excluded.
  • One line of code replaces a dozen lines of nested AnimatablePair<AnimatablePair<…>, …> boilerplate.

visionOS: 3D views you can grab and rest on a table. manipulable() lets users grab, rotate, and scale a view with gestures; surfaceSnappingInfo tells you whether the view has snapped to a real surface.

import ARKit
import RealityKit
import SwiftUI

struct BackpackWaterBottle: View {
    @Environment(\.surfaceSnappingInfo) var snappingInfo: SurfaceSnappingInfo

    var body: some View {
        VStackLayout().depthAlignment(.center) {
            waterBottleView
                .manipulable()

            Pedestal()
                .opacity(
                    snappingInfo.classification == .table ? 1.0 : 0.0)
        }
    }

    var waterBottleView: some View {
        Model3D(named: "waterBottle")
    }
}

Key points:

  • .manipulable() gives a Model3D view system gestures for grabbing, rotating, and scaling, in one line.
  • @Environment(\.surfaceSnappingInfo) reads the current snapping info — the classification has values like .table, .wall, and .floor.
  • When the user sets the water bottle on a table, the Pedestal underneath shows up; while the bottle floats in midair it stays hidden, which reads better visually.

Takeaways

  • Do this: replace every UIViewRepresentable<UITextView> rich text bridge in your project with native TextEditor + AttributedString.

    • Why it pays off: each bridge averages two to three hundred lines of glue code, with three error-prone spots — selection sync, keyboard toolbar, undo stack. The native API pulls that work back inside SwiftUI, and long-term maintenance cost drops sharply.
    • How to start: list every UIViewRepresentable subclass in your repo, pick a smallest case that only uses bold, italic, and color as a pilot, then migrate harder cases one by one.
  • Do this: audit your toolbar background customizations and move to ToolbarSpacer plus the default Liquid Glass look.

    • Why it pays off: the custom .toolbarBackground colors you set for visual alignment break the new material’s refraction under Liquid Glass, and most likely look ugly once compiled.
    • How to start: use Xcode’s project-wide search for toolbarBackground and ultraThinMaterial, strip the custom values one at a time, regroup buttons with ToolbarSpacer(.fixed, ...), then run a visual regression.
  • Do this: change every hand-written animatableData Shape to use the @Animatable macro.

    • Why it pays off: hand-written nested AnimatablePair is highly templated code — change one property and you change three places. The macro removes this low-value labor at the source.
    • How to start: search for the keyword var animatableData to find candidates, add @Animatable to each, mark discrete properties with @AnimatableIgnored, and run UI tests to confirm the animation curves match.
  • Do this: in your visionOS project, evaluate adding manipulable() to every Model3D view.

    • Why it pays off: in a spatial environment, the user’s first instinct is to grab things with their hands. You used to write a DragGesture + RotateGesture3D combination yourself; one modifier now does it.
    • How to start: drop .manipulable() into a demo scene first, then read the snap state with surfaceSnappingInfo and decide whether to show a pedestal, shadow, or other helper visuals once the view snaps.

Comments

GitHub Issues · utterances