Live Activities essentials
Build and tune Live Activities with ActivityKit, WidgetKit, push updates, Dynamic Island landscape layouts, StandBy, small family, and App Intents.
TL;DR
- Model Live Activity data with
ActivityAttributesfor static values and nestedContentStatefor dynamic values so updates stay efficient. - Build Lock Screen and Dynamic Island presentations in a WidgetKit
ActivityConfiguration, usingActivityViewContextto read attributes and current state. - Start and update activities locally with ActivityKit, or use ActivityKit push notifications; choose broadcast updates for large shared events and device-targeted pushes for specific activities.
- Optimize for iOS 27 Dynamic Island landscape width, StandBy backgrounds, Apple Watch/CarPlay small family layouts, and quick actions using
LiveActivityIntent.
Where Live Activities appear
Live Activities provide glanceable, time-sensitive updates for ongoing tasks and events. The session uses a coffee order Live Activity to show changing order phases, estimated ready time, and a short post-order rating interaction.
On iPhone, Live Activities appear on the Lock Screen and in the Dynamic Island. In iOS 27, Dynamic Island compact and minimal presentations are visible in both portrait and landscape. Live Activities also appear in StandBy, in the Apple Watch Smart Stack, in the macOS menu bar, and on the CarPlay Dashboard.
- Design the experience around the key information people need over time.
- Use larger presentations for richer status and context, and compact/minimal presentations for only the most essential signal.
- Consult the Human Interface Guidelines and the "Design dynamic Live Activities" session for design guidance.
Model static and dynamic data
Live Activities treat static and dynamic data differently. Static values go in a type conforming to ActivityAttributes; only values in the nested ContentState can change during the activity lifetime.
For the coffee order, the shop name, drink, and order ID are static. The order phase, estimated ready date, and optional rating are dynamic.
- Keep the model small and update-focused so ActivityKit updates are fast.
- Put server identifiers in attributes when they do not change, so intents and updates can associate UI actions with the right backend object.
- Use
staleDateinActivityContentwhen the current content should be considered out-of-date; the example leaves it unset for the coffee order.
Define Live Activity attributes and content state
Static order metadata lives on DrinkOrderAttributes; mutable status values live in ContentState.
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?
}
}Build the WidgetKit presentations
A Live Activity UI is implemented in a widget extension with WidgetKit. ActivityConfiguration binds the widget to an ActivityAttributes type and supplies SwiftUI views for the Lock Screen and Dynamic Island.
Each view receives an ActivityViewContext, which contains the immutable attributes and the latest content state. The Dynamic Island requires separate compact leading, compact trailing, minimal, and expanded-region views.
- Use the content closure for the Lock Screen-style presentation.
- Provide compact and minimal Dynamic Island views that reduce the interface to the most important information.
- Use
DynamicIslandExpandedRegionclosures for richer expanded Dynamic Island layouts.
Create Live Activity and Dynamic Island views
ActivityConfiguration defines the Lock Screen presentation and all Dynamic Island variants for one activity type.
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 activities
ActivityKit can start a Live Activity directly while the app is running in the foreground. The flow is to check authorization, create attributes, create the initial ContentState, wrap it in ActivityContent, and call Activity.request.
While the activity is running, call update with a new ActivityContent. Activities can also be started or updated by push notifications, and can be scheduled to start at a specific time.
- Use local ActivityKit updates when the app is running and has the current state.
- Use ActivityKit push notifications for background updates.
- Use broadcast updates when many people are following the same shared event, such as a game score.
- Use device-targeted push notifications for per-user or per-device activity updates by obtaining the activity push token.
Start and update a Live Activity locally
The local ActivityKit path checks authorization, requests a new activity, then updates dynamic state with activity.update.
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)
}
await activity.update(
ActivityContent(
state: DrinkOrderAttributes.ContentState(
phase: .preparing,
estimatedReadyDate: estimatedReadyDate
),
staleDate: nil
)
)Optimize presentations and add actions
The session focuses on several refinements for real-world surfaces. In iOS 27 landscape, Dynamic Island compact and minimal views cannot grow in width, so compact views should switch to narrower alternatives when isDynamicIslandLimitedInWidth is true.
StandBy uses the Lock Screen view scaled up, so backgrounds may need different treatment. Use showsWidgetContainerBackground to decide when to apply the regular widget background, and use activityBackgroundTint to provide an edge-to-edge tint in StandBy.
For Apple Watch Smart Stack and CarPlay, declare support for the small activity family and branch on activityFamily to provide a dedicated small layout. For quick contextual actions, associate Live Activity buttons with App Intents conforming to LiveActivityIntent.
- Use an icon or abbreviated UI when the Dynamic Island is width-constrained.
- Use
.supplementalActivityFamilies([.small])before branching to a.small-specific view. - Use
LiveActivityIntentfor actions such as rating an order from the Live Activity UI.
Adapt compact Dynamic Island UI for limited width
isDynamicIslandLimitedInWidth lets compact views switch to a narrower layout in landscape.
struct CompactTrailingView: View {
@Environment(\.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)
}
}
}Support small family and Live Activity button intents
The small family enables adapted Apple Watch and CarPlay layouts; LiveActivityIntent powers quick actions from the Live Activity.
struct DrinkOrderLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DrinkOrderAttributes.self) { context in
ActivityView(context: context)
} dynamicIsland: { context in
// Dynamic Island views...
}
.supplementalActivityFamilies([.small])
}
}
struct RateDrinkIntent: LiveActivityIntent {
static var title: LocalizedStringResource = "Rate Drink"
@Parameter(title: "Order ID") var orderID: String
@Parameter(title: "Positive") var isPositive: Bool
func perform() async throws -> some IntentResult {
await updateLocalDatastore(
rating: isPositive ? .great : .poor,
dismissPolicy: .after(.now + 15)
)
return .result()
}
}Resources
- Human Interface Guidelines: Live Activities
- Starting and updating Live Activities with ActivityKit push notifications
- ActivityKit
Related Sessions
What’s new in Wallet
Learn how iOS 27 Wallet passes add Poster Generic, new barcode formats, featured actions, and Pass Designer/Pass Builder workflows.
Create live communication experiences
Use LiveCommunicationKit to present VoIP conversations in system UI, handle incoming and outgoing calls, and manage group membership and merging.