Implementing ActivityKit for Live Activities in iOS 16
This guide provides a step-by-step walkthrough for developers looking
to integrate ActivityKit into iOS 16 applications to power Live
Activities. Live Activities allow apps to display real-time, glanceable
updates on the iPhone Lock Screen and within the Dynamic Island. Below,
you will learn how to configure your project, define data models using
ActivityAttributes, design the presentation interface with
WidgetKit, and control the activity lifecycle through Swift code.
1. Configure the Project
Before writing Swift code, enable Live Activities in your app's configuration:
- Open your main app target's
Info.plistfile. - Add the key
NSSupportsLiveActivities(Supports Live Activities) and set its Boolean value toYES. - If your project does not already have one, add a Widget Extension target via File > New > Target > Widget Extension, ensuring the Include Live Activity checkbox is selected.
2. Define the Activity Attributes
ActivityKit uses the ActivityAttributes protocol to
structure both static and dynamic data. Static data remains constant
throughout the activity, while dynamic data updates periodically.
import ActivityKit
import Foundation
struct DeliveryAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable {
// Dynamic data that updates frequently
var status: String
var estimatedDeliveryTime: Date
}
// Static data defined when the activity starts
var orderNumber: String
}3. Build the User Interface with WidgetKit
Live Activities rely on SwiftUI and WidgetKit for rendering. You define presentations for both the Lock Screen banner and the Dynamic Island (expanded, compact leading/trailing, and minimal views).
import WidgetKit
import SwiftUI
struct DeliveryActivityWidget: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: DeliveryAttributes.self) { context in
// Lock Screen and banner UI
VStack(alignment: .leading) {
Text("Order #\(context.attributes.orderNumber)")
.font(.headline)
Text(context.state.status)
.font(.subheadline)
}
.padding()
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI
DynamicIslandExpandedRegion(.leading) {
Text("Order #\(context.attributes.orderNumber)")
}
DynamicIslandExpandedRegion(.trailing) {
Text(context.state.status)
}
DynamicIslandExpandedRegion(.bottom) {
Text("Arriving: \(context.state.estimatedDeliveryTime, style: .time)")
}
} compactLeading: {
Image(systemName: "box.truck")
} compactTrailing: {
Text(context.state.status)
} minimal: {
Image(systemName: "box.truck")
}
}
}
}4. Manage the Activity Lifecycle
You manage Live Activities within your main app using the
Activity<Attributes> API to request, update, and end
sessions.
Starting an Activity
Ensure the user has enabled Live Activities using
ActivityAuthorizationInfo().areActivitiesEnabled, then
request a new session:
func startDeliveryActivity() {
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
let attributes = DeliveryAttributes(orderNumber: "12345")
let initialContentState = DeliveryAttributes.ContentState(
status: "Preparing Order",
estimatedDeliveryTime: Date().addingTimeInterval(1800)
)
do {
_ = try Activity.request(
attributes: attributes,
contentState: initialContentState,
pushType: nil // Use .token for APNs remote updates
)
} catch {
print("Failed to start activity: \(error.localizedDescription)")
}
}Updating an Activity
To update the dynamic data, provide a new ContentState
to the running activity instance:
func updateDeliveryActivity(activity: Activity<DeliveryAttributes>, newStatus: String) {
Task {
let updatedContentState = DeliveryAttributes.ContentState(
status: newStatus,
estimatedDeliveryTime: Date().addingTimeInterval(900)
)
await activity.update(using: updatedContentState)
}
}Ending an Activity
When the task is complete, end the activity and specify a dismissal policy:
func endDeliveryActivity(activity: Activity<DeliveryAttributes>) {
Task {
let finalContentState = DeliveryAttributes.ContentState(
status: "Delivered",
estimatedDeliveryTime: Date()
)
// .default keeps the view visible for up to 4 hours; .immediate removes it instantly
await activity.end(using: finalContentState, dismissalPolicy: .default)
}
}5. Remote Updates via Push Notifications
For background updates initiated from your server, set
pushType: .token when calling
Activity.request. Observe the
activity.pushTokenUpdates asynchronous sequence in your
app, send the received push token to your backend server, and use Apple
Push Notification service (APNs) payloads containing the updated
ContentState dictionary to refresh the interface
remotely.