2026 SwiftUI & UI FrameworksApp Services
WWDC26 · 15 min · SwiftUI & UI Frameworks / App Services
Live Activities essentials
Elevate your app experience with Live Activities. Explore many of the places where Live Activities appear, including a new style in the Dynamic Island that delivers more information when iPhone is used in landscape. Learn how to tailor your Live Activity for each space, structure your content and data, and drive real time updates from start to finish using ActivityKit and push notifications.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 9 snippets
Define initial Live Activity
// Define initial Live Activity.
import ActivityKit
import Foundation
public struct DrinkOrderAttributes: ActivityAttributes {
let shopName: String
let drink: Drink
let orderID: UUID
public struct ContentState: Codable, Hashable {
var phase: DrinkOrder.Phase = .waiting
var estimatedReadyDate: Date
var rating: DrinkOrder.Rating?
}
} Create each Live Activity view
// Create each Live Activity view
import ActivityKit
import SwiftUI
import WidgetKit
struct DrinkOrderLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DrinkOrderAttributes.self) { context in
ActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
ExpandedLeadingView(context: context)
}
DynamicIslandExpandedRegion(.center) {
ExpandedCenterView(context: context)
}
DynamicIslandExpandedRegion(.trailing) {
ExpandedTrailingView(context: context)
}
DynamicIslandExpandedRegion(.bottom) {
ExpandedBottomView(context: context)
}
} compactLeading: {
CompactLeadingView(context: context)
} compactTrailing: {
CompactTrailingView(context: context)
} minimal: {
MinimalView(context: context)
}
}
}
} Start and update a Live Activity
// Start a Live Activity
func launchLiveActivity(order: DrinkOrder) throws {
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
let attributes = DrinkOrderAttributes(shopName: "Coffee Shop", drink: order.drink, orderID: order.id)
let estimatedReadyDate = Date.now + (15 * 60)
let contentState = DrinkOrderAttributes.ContentState(phase: .waiting, estimatedReadyDate: estimatedReadyDate)
let activityContent = ActivityContent(state: contentState, staleDate: nil)
let activity = try Activity.request(attributes: attributes, content: activityContent)
}
// Update a Live Activity
await activity.update(
ActivityContent(
state: DrinkOrderAttributes.ContentState(
phase: .preparing,
estimatedReadyDate: estimatedReadyDate
),
staleDate: nil
)
) Optimize for limited width in the Dynamic Island
// Optimize for limited width in the Dynamic Island
struct CompactTrailingView: View {
(\.isDynamicIslandLimitedInWidth) var isDynamicIslandLimitedInWidth
var context: ActivityViewContext<DrinkOrderAttributes>
var body: some View {
if isDynamicIslandLimitedInWidth {
StepProgressIconView(context: context)
} else if context.state.phase.showsTimer {
EstimatedReadyView(context: context, font: .system(.body).monospacedDigit())
.multilineTextAlignment(.trailing)
.frame(maxWidth: maximumTimerLabelWidth)
} else {
OrderPhaseLabelView(context: context, font: .caption2.bold(), color: .brown)
.multilineTextAlignment(.trailing)
}
}
} Extend background color in StandBy
// Extend background color in StandBy
struct ActivityView: View {
(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
var context: ActivityViewContext<DrinkOrderAttributes>
var body: some View {
DetailView(context: context)
.background {
if showsWidgetContainerBackground {
LinearGradient.barista
}
}
.activityBackgroundTint(.espresso)
}
} Add support for activityFamily small
// Add support for activityFamily small
import ActivityKit
import SwiftUI
import WidgetKit
struct DrinkOrderLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DrinkOrderAttributes.self) { context in
ActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
ExpandedLeadingView(context: context)
}
DynamicIslandExpandedRegion(.center) {
ExpandedCenterView(context: context)
}
DynamicIslandExpandedRegion(.trailing) {
ExpandedTrailingView(context: context)
}
DynamicIslandExpandedRegion(.bottom) {
ExpandedBottomView(context: context)
}
} compactLeading: {
CompactLeadingView(context: context)
} compactTrailing: {
CompactTrailingView(context: context)
} minimal: {
MinimalView(context: context)
}
}
.supplementalActivityFamilies([.small])
}
} Optimize for small family
// Optimize for small family
struct ActivityView: View {
(\.showsWidgetContainerBackground) var showsWidgetContainerBackground
(\.activityFamily) var activityFamily
var context: ActivityViewContext<DrinkOrderAttributes>
var body: some View {
contentView
.background {
if showsWidgetContainerBackground {
LinearGradient.barista
}
}
.activityBackgroundTint(.espresso)
}
var contentView: some View {
if activityFamily == .small {
SmallView(context: context)
} else {
DetailView(context: context)
}
}
} Add interactivity with App Intents
// Add interactivity with App Intents
struct RateDrinkIntent: LiveActivityIntent {
static var title: LocalizedStringResource = "Rate Drink"
(title: "Order ID")
var orderID: String
(title: "Positive")
var isPositive: Bool
func perform() async throws -> some IntentResult {
await updateLocalDatastore(rating: isPositive ? .great : .poor, dismissPolicy: .after(.now + 15))
return .result()
}
} Associate an intent with a button
// Associate an intent with a button
struct RatingButtons: View {
var context: ActivityViewContext<DrinkOrderAttributes>
var body: some View {
HStack(spacing: 12) {
Button(intent: RateDrinkIntent(
orderID: context.attributes.orderID.uuidString, isPositive: false)) {
Label("Not Good", systemImage: "hand.thumbsdown.fill")
}
.buttonStyle(RatingButtonStyle(color: .red))
Button(intent: RateDrinkIntent(
orderID: context.attributes.orderID.uuidString, isPositive: true)) {
Label("Great", systemImage: "hand.thumbsup.fill")
}
.buttonStyle(RatingButtonStyle(color: .green))
}
}
} Resources
Related sessions
-
15 min -
24 min -
10 min