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 ↗Chapters
Code shown on screen · 10 snippets
Define ActivityAttributes
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
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
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
// 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
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
import WidgetKit
import SwiftUI
@main
struct EmojiRangersWidgetBundle: WidgetBundle {
var body: some Widget {
EmojiRangerWidget()
LeaderboardWidget()
AdventureActivityConfiguration()
}
} Define Lock Screen presentation
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
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
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
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
Related sessions
-
19 min -
18 min -
15 min