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

What's new in SwiftUI

Watch original video

Highlight

SwiftUI gained cross-platform capability expansion at WWDC23: new visionOS scene types (WindowGroup, Volume, ImmersiveSpace), @Observable macro instead of ObservableObject, SwiftData native integration, KeyframeAnimator and PhaseAnimator animation APIs, and ScrollView’s scrolling transition and alignment capabilities.

Core Content

SwiftUI enters the era of spatial computing

Previously, developing 3D interfaces required learning a whole new framework. After the release of visionOS, this threshold was broken.

(01:55) SwiftUI supports three scene types on visionOS.WindowGroupRendered as a 2D window, the control comes with depth-sensitive 3D effects.WindowGroupYou can use it as usualNavigationSplitViewTabViewetc. containers.

(02:26) declares the scene as.volumetricstyle, you get a Volume. Volume displays 3D content, such as board games or architectural models, in a limited space, side by side with other applications. useModel3DTo display a static model, useRealityViewDisplay dynamic models with lighting and interactivity.

02:59ImmersiveSpaceThe scene type defines the immersive space experience and supports two immersion styles: mixed and full. The mixed style overlays virtual content onto the real environment, while the full style allows the application to completely take over the user’s field of vision.

A new design for watchOS 10

(04:08) watchOS 10 features a full-screen color design.NavigationSplitViewandNavigationStackGet a new transition animation,TabViewAdded vertical paging style driven by Digital Crown.

04:53containerBackgroundModifier configures subtle background gradients for navigation and tab views, with animated transitions on push and pop.containerBackgroundAlso supportsTabView

Widgets enter more scenes

(05:54) Widgets appear in more places: on the lock screen in iPadOS 17, in Standby mode on iPhone, on the desktop in macOS Sonoma. More importantly, widgets now support interactive controls.

06:32ToggleandButtonIn-app code can be triggered through App Intents, and widgets can be animated using SwiftUI’s transition and animation modifiers.

Data flow revolution: @Observable and SwiftData

10:44@ObservableMacros are the biggest upgrade to SwiftUI data flow. Add to class@Observable, no need to mark@Published, SwiftUI automatically tracks the read properties and only refreshes the view when the properties actually used change.

(13:16) SwiftData is a new data modeling and management framework. Bundle@ObservableReplace with@ModelMacro, the model gains persistence capabilities while retaining all the benefits of Observable. useQueryThe property wrapper gets data from the database, and the view automatically updates when the data changes.

Detailed Content

visionOS scene type

(01:55) on visionOSWindowGroupThe same way as on iOS:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Key points:

  • WindowGroupRendered as a 2D flat window on visionOS
  • Controls within the window automatically get depth-sensitive 3D effects
  • Containers such asNavigationSplitViewTabViewCan be used directly

Volume needs to be setvolumetricstyle:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .windowStyle(.volumetric)
    }
}

Key points:

  • .windowStyle(.volumetric)Turn window into 3D volume
  • useModel3DLoad static USDZ model
  • useRealityViewCreate dynamic 3D scenes with lighting, physics, and interactivity

How ImmersiveSpace is declared:

@main
struct MyApp: App {
    var body: some Scene {
        ImmersiveSpace(id: "space") {
            ImmersiveView()
        }
        .immersionStyle(selection: .constant(.mixed), in: .mixed, .full)
    }
}

Key points:

  • ImmersiveSpaceis an independent scene type -.immersionStyleSpecify supported immersion modes
  • mixed mode superimposes content onto the real environment
  • full mode completely takes over the user’s field of view

@Observable macro

(10:44) used beforeObservableObjectEach attribute needs to be added@Published

class DogModel: ObservableObject {
    @Published var dogs: [Dog] = []
    @Published var isFavorite: Bool = false
}

Now just one line:

@Observable class DogModel {
    var dogs: [Dog] = []
    var isFavorite: Bool = false
}

No property wrapper is required when used in a view:

struct DogCard: View {
    let model: DogModel

    var body: some View {
        VStack {
            Text(model.dogs.first?.name ?? "")
            Toggle("Favorite", isOn: $model.isFavorite)
        }
    }
}

Key points:

  • @ObservableMacros automatically expand to observable types
  • No need@Published, all stored properties are observable by default
  • Read properties directly in the view, no need@ObservedObject- SwiftUI only tracks the properties that are actually read, and unread property changes will not trigger a refresh.
  • No unnecessary updates will be triggered when passing intermediate views

SwiftData integration

(13:16) put@ObservableReplace with@Model, the model is persisted:

import SwiftData

@Model
class Dog {
    var name: String
    var breed: String
    var spottedDate: Date

    init(name: String, breed: String, spottedDate: Date = .now) {
        self.name = name
        self.breed = breed
        self.spottedDate = spottedDate
    }
}

Set up the model container in the App:

@main
struct DogWatcherApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Dog.self)
    }
}

Query data in the view:

import SwiftData

struct DogList: View {
    @Query(sort: \Dog.spottedDate, order: .reverse) private var dogs: [Dog]

    var body: some View {
        List(dogs) { dog in
            Text(dog.name)
        }
    }
}

Key points:

  • @ModelMacros provide both Observable and persistence capabilities -.modelContainer(for:)Set up the storage stack at the scene level -@QueryAutomatically obtain data from the database and refresh the view when the data changes -sortParameters specify sorting rules
  • Supports advanced queries such as filtering and paging

Keyframe Animator animation

19:07KeyframeAnimatorSupports multi-attribute parallel animation:

struct LogoAnimation: View {
    @State private var trigger = false

    var body: some View {
        KeyframeAnimator(
            initialValue: AnimationValues(),
            trigger: trigger
        ) { values in
            Image("logo")
                .offset(y: values.verticalTranslation)
                .rotationEffect(values.rotation)
        } keyframes: { _ in
            KeyframeTrack(\.verticalTranslation) {
                SpringKeyframe(30, duration: 0.25, spring: .smooth)
                CubicKeyframe(-50, duration: 0.3)
                SpringKeyframe(0, spring: .bouncy)
            }
            KeyframeTrack(\.rotation) {
                LinearKeyframe(.degrees(0), duration: 0.25)
                CubicKeyframe(.degrees(360), duration: 0.55)
            }
        }
    }
}

struct AnimationValues {
    var verticalTranslation: CGFloat = 0
    var rotation: Angle = .zero
}

Key points:

  • initialValueContains all animatable properties -triggerIs an Equatable state, triggering animation when changing
  • The first closure builds the view with animated values
  • The second closure defines the trajectory of each attribute over time -KeyframeTracksupportSpringKeyframeCubicKeyframeLinearKeyframe- Multiple tracks are executed in parallel

Phase Animator animation

20:29PhaseAnimatorExecute a series of animation stages in sequence:

struct WatchButton: View {
    @State private var sightingCount = 0

    var body: some View {
        Button {
            sightingCount += 1
        } label: {
            Image("dog-icon")
                .phaseAnimator(
                    [AnimationPhase.idle, .rotate, .grow, .settle],
                    trigger: sightingCount
                ) { content, phase in
                    content
                        .rotationEffect(phase.rotation)
                        .scaleEffect(phase.scale)
                } animation: { phase in
                    switch phase {
                    case .idle: .default
                    case .rotate: .spring(.snappy)
                    case .grow: .spring(duration: 0.3, bounce: 0.5)
                    case .settle: .spring(.bouncy)
                    }
                }
        }
    }
}

enum AnimationPhase: CaseIterable {
    case idle, rotate, grow, settle

    var rotation: Angle {
        switch self {
        case .idle: .zero
        case .rotate: .degrees(15)
        case .grow, .settle: .zero
        }
    }

    var scale: CGFloat {
        switch self {
        case .idle, .rotate: 1.0
        case .grow: 1.3
        case .settle: 1.0
        }
    }
}

Key points:

  • PhaseAnimatorExecute stages in sequence, starting the next stage only after the previous stage is completed -triggerExecute all phases from scratch on change
  • Different animation curves can be defined for each stage
  • New spring animation supportdurationandbounceparameters -.snappy.bouncy.smoothIs the default spring configuration

ScrollView improvements

27:53scrollTransitionAdd visual effects to scroll items:

ScrollView {
    LazyVStack {
        ForEach(dogs) { dog in
            DogCard(dog: dog)
                .scrollTransition { content, phase in
                    content
                        .opacity(phase.isIdentity ? 1 : 0.5)
                        .scaleEffect(phase.isIdentity ? 1 : 0.8)
                }
        }
    }
}

Key points:

  • scrollTransitionApply effects when items enter/leave the visible area -phaseInclude.topLeading.identity.bottomTrailingthree states
  • No needGeometryReader

Pagination and alignment:

ScrollView(.horizontal) {
    LazyHStack {
        ForEach(parks) { park in
            ParkCard(park: park)
                .containerRelativeFrame(.horizontal, count: 3, span: 1, spacing: 16)
        }
    }
    .scrollTargetLayout()
}
.scrollTargetBehavior(.viewAligned)

Key points:

  • containerRelativeFrameMake subview size relative to container -countDivide the screen into several parts,spanSpecify how many shares to occupy -scrollTargetLayoutMark the layout that needs to be aligned -scrollTargetBehavior(.viewAligned)Align to view bounds when scrolling stops
  • also supports.pagingBehavior

Tactile feedback

(22:03) newsensoryFeedbackModifier:

Button("Record Sighting") {
    sightingCount += 1
}
.sensoryFeedback(.success, trigger: sightingCount)

Key points:

  • .success.warning.errorWait for predefined feedback types -triggerPlay haptic feedback when changing
  • Supports all platforms with haptic feedback

SF Symbol animation effect

24:55symbolEffectModifiers add animation to SF Symbol:

Image(systemName: "slider.horizontal.3")
    .symbolEffect(.bounce, options: .repeat(1))

Key points:

  • .pulse.variableColoris a continuous animation -.scale.appear.disappear.replaceIs a state change animation -.bounceIt is an event notification animation
  • Can be applied to a single symbol or the entire view hierarchy

Core Takeaways

  1. Make a visionOS 3D display application

    • useWindowGroup + .volumetricStyle showcase product 3D model
    • useModel3DLoad the USDZ file, the user can use gestures to rotate and view
    • Entrance:Model3D(named: "product")and.windowStyle(.volumetric)
  2. Add interactive widgets to existing applications

    • Upgrade the existing Today Widget to an interactive version
    • useToggleandButton+ App Intents allow users to operate directly on widgets
    • Entrance:#Preview(as: .systemSmall)preview widget,AppIntentDefine operations
  3. Reconstruct the data layer using @Observable

    • put existingObservableObject + @PublishedThe model is replaced by@Observable- Delete large amounts@ObservedObjectand@EnvironmentObjectcode
    • Entrance: Putclass Model: ObservableObjectChange to@Observable class Model
  4. Make an onboarding process with animation

    • usePhaseAnimatorCreate step-by-step animations
    • Different symbol animations and view transitions are triggered when each step switches
    • Entrance:PhaseAnimator(phases, trigger: step) + symbolEffect
  5. Make a ScrollView driven gallery

    • Horizontal scrolling display of images, each item takes up 80% of the screen width
    • usescrollTransitionMake edge items shrink and become transparent
    • usescrollTargetBehavior(.viewAligned)Achieve card adsorption effect
    • Entrance:containerRelativeFrame(.horizontal, count: 5, span: 4)

Comments

GitHub Issues · utterances