Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 EssentialsApp Services

WWDC23 · 17 min · Essentials / App Services

Meet ActivityKit

Live Activities are a glanceable way for someone to keep track of the progress of a task within your app. We’ll teach you how you can create helpful experiences for the Lock Screen, the Dynamic Island, and StandBy. Learn how to update your app’s Live Activities, monitor activity state, and take advantage of WidgetKit and SwiftUI to build richer experiences.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 10 snippets

Define ActivityAttributes swift · at 5:40 ↗
import ActivityKit

struct AdventureAttributes: ActivityAttributes {
    let hero: EmojiRanger

    struct ContentState: Codable & Hashable {
        let currentHealthLevel: Double
        let eventDescription: String
    }
}
Request Live Activity with initial content state swift · at 6:28 ↗
let adventure = AdventureAttributes(hero: hero)

let initialState = AdventureAttributes.ContentState(
    currentHealthLevel: hero.healthLevel,
    eventDescription: "Adventure has begun!"
)
let content = ActivityContent(state: initialState, staleDate: nil, relevanceScore: 0.0)

let activity = try Activity.request(
    attributes: adventure,
    content: content,
    pushType: nil
)
Update Live Activity with new content swift · at 8:00 ↗
let heroName = activity.attributes.hero.name               
let contentState = AdventureAttributes.ContentState(
    currentHealthLevel: hero.healthLevel,
    eventDescription: "\(heroName) has taken a critical hit!"
)

var alertConfig = AlertConfiguration(
    title: "\(heroName) has taken a critical hit!",
    body: "Open the app and use a potion to heal \(heroName)",
    sound: .default
)  
     
activity.update(
    ActivityContent<AdventureAttributes.ContentState>(
        state: contentState,
        staleDate: nil
    ),
    alertConfiguration: alertConfig
)
Observe activity state swift · at 9:30 ↗
// Observe activity state asynchronously
func observeActivity(activity: Activity<AdventureAttributes>) {
    Task {
        for await activityState in activity.activityStateUpdates {
            if activityState == .dismissed {
                self.cleanUpDismissedActivity()
            }
        }
    }
}

// Observe activity state synchronously
let activityState = activity.activityState
if activityState == .dismissed {
    self.cleanUpDismissedActivity()
}
Dismiss Live Activity with final content state swift · at 10:03 ↗
let hero = activity.attributes.hero

let finalContent = AdventureAttributes.ContentState(
    currentHealthLevel: hero.healthLevel,
    eventDescription: "Adventure over! \(hero.name) has defeated the boss! Congrats!"
)

let dismissalPolicy: ActivityUIDismissalPolicy = .default

activity.end(
    ActivityContent(state: finalContent, staleDate: nil),
    dismissalPolicy: dismissalPolicy)
}
Add ActivityConfiguration to WidgetBundle swift · at 10:50 ↗
import WidgetKit
import SwiftUI

@main
struct EmojiRangersWidgetBundle: WidgetBundle {
    var body: some Widget {
        EmojiRangerWidget()
        LeaderboardWidget()
        AdventureActivityConfiguration()
    }
}
Define Lock Screen presentation swift · at 11:05 ↗
struct AdventureActivityConfiguration: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: AdventureAttributes.self) { context in
            AdventureLiveActivityView(
                hero: context.attributes.hero,
                isStale: context.isStale,
                contentState: context.state
            )
            .activityBackgroundTint(Color.navyBlue)
        } dynamicIsland: { context in
            // ...
        }
    }
}
Define Dynamic Island compact presentation swift · at 13:28 ↗
struct AdventureActivityConfiguration: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: AdventureAttributes.self) { context in
            // ...
        } dynamicIsland: { context in
            DynamicIsland {
                // ...
            } compactLeading: {
                Avatar(hero: context.attributes.hero)
            } compactTrailing: {
                ProgressView(value: context.state.currentHealthLevel) {
                    Text("\(Int(context.state.currentHealthLevel * 100))")
                }
                .progressViewStyle(.circular)
                .tint(context.state.currentHealthLevel <= 0.2 ? Color.red : Color.green)
            } minimal: {
                // ...
            }
        }
    }
}
Define Dynamic Island minimal presentation swift · at 14:42 ↗
struct AdventureActivityConfiguration: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: AdventureAttributes.self) { context in
            // ...
        } dynamicIsland: { context in
            DynamicIsland {
                // ...
            } compactLeading: {
                // ...
            } compactTrailing: {
                // ...
            } minimal: {
                ProgressView(value: context.state.currentHealthLevel) {
                    Avatar(hero: context.attributes.hero)
                }
                .progressViewStyle(.circular)
                .tint(context.state.currentHealthLevel <= 0.2 ? Color.red : Color.green)
            }
        }
    }
}
Define Dynamic Island expanded presentation swift · at 15:26 ↗
struct AdventureActivityConfiguration: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: AdventureAttributes.self) { context in
            // ...
        } dynamicIsland: { context in
            DynamicIsland {
                // Leading region
                DynamicIslandExpandedRegion(.leading) {
                    LiveActivityAvatarView(hero: hero)
                }

                // Expanded region
                DynamicIslandExpandedRegion(.trailing) {
                    StatsView(hero: hero, isStale: isStale)
                }

                // Bottom region
                DynamicIslandExpandedRegion(.bottom) {
                    HealthBar(currentHealthLevel: contentState.currentHealthLevel)
                    EventDescriptionView(hero: hero, contentState: contentState)
                }
            } compactLeading: {
                // ...
            } compactTrailing: {
                // ...
            } minimal: {
                // ...
            }
        }
    }
}

Resources